How OAuthSecurity to obtain emails for different oauth clients, but Microsoft Client doesn’t return email, it didn’t include scope “wl.emails”

I have been playing with MVC 4, SimpleMembership, WebSecurity and OAuthWebSecurity for a while now. I can see the idea of OAuthWebsecurity is a wrapper around DotNetOpenAuth. It registers the clients in AuthConfig.cs file. That really works and helps me reduce heaps lines of code.

But soon enough,

1. I find only Google client returns an email as a username. I mean email is still quite important for the newsletters or system emails etc.

2. For Twitter, they don’t provide email via OAuth or any API, which is a shame. But I don’t complain. (Maybe they have changed without my awareness). So we don’t do anything with it.

3. For Facebook, with DotNetOpenAuth and OAuth2, it actually includes the scope “email” and returns email in the “ExtraData” dictionary.

So in the ExternalLoginCallback() method, you can find this line:

            AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));

If you query result.ExtraData[“username”], that contains the user’s email in it.

4. For Microsoft, it is a nightmare, I find DotNetOpenAuth didn’t even include the scope “wl.emails” in their request at all. I am disappointed, but it is not the end of the world.

I am trying to create a Custom Authentication Client, to retrieve Microsoft emails.

First, create a class MicrosoftScopedClient and implement IAuthenticationClient interface. You must implement two methods of that interface.

    public class MicrosoftScopedClient : IAuthenticationClient
    {

        public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
        {
  
        }

        public AuthenticationResult VerifyAuthentication(HttpContextBase context)
        {
        }
    }

The next step is to build the authentication url in method “RequestAuthentication()”,

public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
        {
            string url = baseUrl + "?client_id=" + clientId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + HttpUtility.UrlEncode(scope) + "&response_type=code";
            context.Response.Redirect(url);
        }

Then I build VerifyAuthentication() method to receive the authentication code and send requests to obtain the access_token, and then use the access_token to request for the profiles.

public AuthenticationResult VerifyAuthentication(HttpContextBase context)
        {
            string code = context.Request.QueryString["code"];

            string rawUrl = context.Request.Url.ToString();
            //From this we need to remove code portion
            rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");

            IDictionary userData = GetUserData(code, rawUrl);

            if (userData == null)
                return new AuthenticationResult(false, ProviderName, null, null, null);

            string id = userData["id"];
            string username = userData["email"];
            userData.Remove("id");
            userData.Remove("email");

            AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
            return result;
        }

After I have done the work of building the MicrosoftScopedClient, I need to register it in the AuthConfig.cs, now we can feel free to pass any scopes there =)

            OAuthWebSecurity.RegisterClient(new MicrosoftScopedClient(ConfigurationManager.AppSettings["Microsoft.ClientId"].ToString(),
                ConfigurationManager.AppSettings["Microsoft.Secret"].ToString(),
                "wl.basic wl.emails"
                )
                , "Microsoft", null);

Below is a full copy of the MicrosoftScopedClient,

using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;
using DotNetOpenAuth.Messaging;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace MicrosoftClient.Filters
{
    public class MicrosoftScopedClient : IAuthenticationClient
    {
        private string clientId;
        private string clientSecret;
        private string scope;

        private const string baseUrl = "https://login.live.com/oauth20_authorize.srf";
        private const string tokenUrl = "https://login.live.com/oauth20_token.srf";

        public MicrosoftScopedClient(string clientId, string clientSecret, string scope)
        {
            this.clientId = clientId;
            this.clientSecret = clientSecret;
            this.scope = scope;
        }

        public string ProviderName
        {
            get { return "Microsoft"; }
        }

        public void RequestAuthentication(HttpContextBase context, Uri returnUrl)
        {
            string url = baseUrl + "?client_id=" + clientId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=" + HttpUtility.UrlEncode(scope) + "&response_type=code";
            context.Response.Redirect(url);
        }

        public AuthenticationResult VerifyAuthentication(HttpContextBase context)
        {
            string code = context.Request.QueryString["code"];

            string rawUrl = context.Request.Url.ToString();
            //From this we need to remove code portion
            rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", "");

            IDictionary userData = GetUserData(code, rawUrl);

            if (userData == null)
                return new AuthenticationResult(false, ProviderName, null, null, null);

            string id = userData["id"];
            string username = userData["email"];
            userData.Remove("id");
            userData.Remove("email");

            AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData);
            return result;
        }

        private IDictionary GetUserData(string accessCode, string redirectURI)
        {
            string token = QueryAccessToken(redirectURI, accessCode);
            if (token == null || token == "")
            {
                return null;
            } 
            var userData = GetUserData(token);
            return userData;
        }

        private IDictionary GetUserData(string accessToken)
        {
            ExtendedMicrosoftClientUserData graph;
            var request =
                WebRequest.Create(
                    "https://apis.live.net/v5.0/me?access_token=" + EscapeUriDataStringRfc3986(accessToken));
            using (var response = request.GetResponse())
            {
                using (var responseStream = response.GetResponseStream())
                {
                    using (StreamReader sr = new StreamReader(responseStream))
                    {
                        string data = sr.ReadToEnd();
                        graph = JsonConvert.DeserializeObject(data);
                    }
                }
            }

            var userData = new Dictionary();
            userData.Add("id", graph.Id);
            userData.Add("username", graph.Name);
            userData.Add("name", graph.Name);
            userData.Add("link", graph.Link == null ? null : graph.Link.AbsoluteUri);
            userData.Add("gender", graph.Gender);
            userData.Add("firstname", graph.FirstName);
            userData.Add("lastname", graph.LastName);
            userData.Add("email", graph.Emails.Preferred);
            return userData;
        }

        private string QueryAccessToken(string returnUrl, string authorizationCode)
        {
            var entity =
                CreateQueryString(
                    new Dictionary {
						{ "client_id", this.clientId },
						{ "redirect_uri", returnUrl },
						{ "client_secret", this.clientSecret},
						{ "code", authorizationCode },
						{ "grant_type", "authorization_code" },
					});

            WebRequest tokenRequest = WebRequest.Create(tokenUrl);
            tokenRequest.ContentType = "application/x-www-form-urlencoded";
            tokenRequest.ContentLength = entity.Length;
            tokenRequest.Method = "POST";

            using (Stream requestStream = tokenRequest.GetRequestStream())
            {
                var writer = new StreamWriter(requestStream);
                writer.Write(entity);
                writer.Flush();
            }

            HttpWebResponse tokenResponse = (HttpWebResponse)tokenRequest.GetResponse();
            if (tokenResponse.StatusCode == HttpStatusCode.OK)
            {
                using (Stream responseStream = tokenResponse.GetResponseStream())
                {
                    using (StreamReader sr = new StreamReader(responseStream))
                    {
                        string data = sr.ReadToEnd();
                        var tokenData = JsonConvert.DeserializeObject(data);
                        if (tokenData != null)
                        {
                            return tokenData.AccessToken;
                        }
                    }
                }
            }

            return null;
        }

        private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
        private static string EscapeUriDataStringRfc3986(string value)
        {
            StringBuilder escaped = new StringBuilder(Uri.EscapeDataString(value));

            // Upgrade the escaping to RFC 3986, if necessary.
            for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
            {
                escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
            }

            // Return the fully-RFC3986-escaped string.
            return escaped.ToString();
        }

        private static string CreateQueryString(IEnumerable<KeyValuePair> args)
        {
            if (!args.Any())
            {
                return string.Empty;
            }
            StringBuilder sb = new StringBuilder(args.Count() * 10);

            foreach (var p in args)
            {
                sb.Append(EscapeUriDataStringRfc3986(p.Key));
                sb.Append('=');
                sb.Append(EscapeUriDataStringRfc3986(p.Value));
                sb.Append('&');
            }
            sb.Length--; // remove trailing &

            return sb.ToString();
        }

        protected class ExtendedMicrosoftClientUserData
        {
            public string FirstName { get; set; }
            public string Gender { get; set; }
            public string Id { get; set; }
            public string LastName { get; set; }
            public Uri Link { get; set; }
            public string Name { get; set; }
            public Emails Emails { get; set; }
        }

        protected class Emails
        {
            public string Preferred { get; set; }
            public string Account { get; set; }
            public string Personal { get; set; }
            public string Business { get; set; }
        }
    }
}

Conditional required validation or field mandatory depends on another field MVC 4

I have experienced this situation that when I need to make a field mandatory if the user has entered a value in another field (or a particular value for that field).

Here is my example, I have two radio buttons says “Do you have the purchase receipt?” with options “yes” or “no”. If the user has selected “yes”, I need them to specify the date of the purchase as well.

image

Now the headache is, I can’t make “Purchase Date” required field. Because if user selects “no”, they don’t need to enter the “Purchase Date”. After I did some research and lookup from the internet. I find this solution on StackOverflow. It has a few bugs and I fixed them and shared over here in my blog.

I created a RequiredIfAttribute,

public class RequiredIfAttribute : ValidationAttribute, IClientValidatable
{
    protected RequiredAttribute _innerAttribute;

    public string DependentProperty { get; set; }
    public object TargetValue { get; set; }

    public bool AllowEmptyStrings
    {
        get
        {
            return _innerAttribute.AllowEmptyStrings;
        }
        set
        {
            _innerAttribute.AllowEmptyStrings = value;
        }
    }

    public RequiredIfAttribute(string dependentProperty, object targetValue)
    {
        _innerAttribute = new RequiredAttribute();
        DependentProperty = dependentProperty;
        TargetValue = targetValue;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // get a reference to the property this validation depends upon
        var containerType = validationContext.ObjectInstance.GetType();
        var field = containerType.GetProperty(DependentProperty);

        if (field != null)
        {
            // get the value of the dependent property
            var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
            // trim spaces of dependent value
            if (dependentValue != null && dependentValue is string)
            {
                dependentValue = (dependentValue as string).Trim();

                if (!AllowEmptyStrings && (dependentValue as string).Length == 0)
                {
                    dependentValue = null;
                }
            }

            // compare the value against the target value
            if ((dependentValue == null && TargetValue == null) ||
                (dependentValue != null && (TargetValue.Equals("*") || dependentValue.Equals(TargetValue))))
            {
                // match => means we should try validating this field
                if (!_innerAttribute.IsValid(value))
                    // validation failed - return an error
                    return new ValidationResult(FormatErrorMessage(validationContext.DisplayName), new[] { validationContext.MemberName });
            }
        }

        return ValidationResult.Success;
    }

    public virtual IEnumerable&lt;ModelClientValidationRule&gt; GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "requiredif",
        };

        string depProp = BuildDependentPropertyId(metadata, context as ViewContext);

        // find the value on the control we depend on;
        // if it's a bool, format it javascript style 
        // (the default is True or False!)
        string targetValue = (TargetValue ?? "").ToString();
        if (TargetValue is bool)
            targetValue = targetValue.ToLower();

        rule.ValidationParameters.Add("dependentproperty", depProp);
        rule.ValidationParameters.Add("targetvalue", targetValue);

        yield return rule;
    }

    private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext)
    {
        // build the ID of the property
        string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(DependentProperty);
        // unfortunately this will have the name of the current field appended to the beginning,
        // because the TemplateInfo's context has had this fieldname appended to it. Instead, we
        // want to get the context as though it was one level higher (i.e. outside the current property,
        // which is the containing object, and hence the same level as the dependent property.
        var thisField = metadata.PropertyName + "_";
        if (depProp.StartsWith(thisField))
            // strip it off again
            depProp = depProp.Substring(thisField.Length);
        return depProp;
    }
}

2. Create js validation and js unobtrusive validation, (I put them in document.ready() callback)

$.validator.addMethod('requiredif',
    function (value, element, parameters) {
        var id = '#' + parameters['dependentproperty'];

        // get the target value (as a string, 
        // as that's what actual value will be)
        var targetvalue = parameters['targetvalue'];
        targetvalue = (targetvalue == null ? '' : targetvalue).toString();

        // get the actual value of the target control
        // note - this probably needs to cater for more 
        // control types, e.g. radios
        var control = $(id);
        var controltype = control.attr('type');
        var actualvalue =
            (controltype === 'checkbox' ||  controltype === 'radio')  ?
            control.attr('checked').toString() :
            control.val();

        // if the condition is true, reuse the existing 
        // required field validator functionality
        if ($.trim(targetvalue) === $.trim(actualvalue) || ($.trim(targetvalue) === '*' && $.trim(actualvalue) !== ''))
            return $.validator.methods.required.call(
              this, value, element, parameters);

        return true;
    });

$.validator.unobtrusive.adapters.add(
    'requiredif',
    ['dependentproperty', 'targetvalue'],
    function (options) {
        options.rules['requiredif'] = {
            dependentproperty: options.params['dependentproperty'],
            targetvalue: options.params['targetvalue']
        };
        options.messages['requiredif'] = options.message;
    });

3. For the Model,

        [Required]
        public bool HasReceipt { get; set; }

         [RequiredIf("HasReceipt", true, ErrorMessage = "You must enter purchase date")]
        [Display(Name="Purchase Date")]
        public DateTime? PurchaseDate { get; set; }

4. When reference this validation js, I notice that it only works before the unobtrusive javascript,


    <script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
    <script src="~/Scripts/jquery.validate.min.js"></script>
    <script src="~/Scripts/jquery.validate.requiredif.js"></script>
    <script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>

5. Now you have your conditional required validations.

There are many scenarios like this,

e.g.

If you have entered an Address field, you must enter Suburb, City.

If you have selected yes for a credit card, you must enter the credit card digits.

If you have subscribed a service, you must enter a valid email.

And many more.

Ninject with custom attribute and filter

I am implementing a custom credit check filter for a controller action. I use Ninject as my Ioc container. First, create a action filter and implement IActionFilter interface, implement the OnActionExecuted() and OnActionExecuting() methods. Within the credit checking functions, it needs a CreditService to check against the repository and database. So I have created the parameterized constructor using Ninject to inject the CreditService object.

 public class CreditCheckFilter : IActionFilter
    {
        private ICreditService _creditService { get; set; }

        public CreditCheckFilter(ICreditService myService)
        {
            _creditService = myService;
        }

        public void OnActionExecuted(ActionExecutedContext filterContext)
        {

        }

        public void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var userName = filterContext.HttpContext.User.Identity.Name;
            var bal = _creditService.GetTokenBalance(WebSecurity.GetUserId(userName));
            if (bal <= 0)
            {
                RouteValueDictionary redirectTargetDictionary = new RouteValueDictionary();
                redirectTargetDictionary.Add("action", "GetTokens");
                redirectTargetDictionary.Add("controller", "Account");
                filterContext.Result = new RedirectToRouteResult(redirectTargetDictionary);
               
            }
        }
    }

Now Let’s create a CreditCheckAttribute that implements FilterAttribute, this is the attribute we put against the controller action,

    public class CreditCheckAttribute : FilterAttribute { }

Now, we need to use Ninject BindFilter() method to bind our CreditCheckFilter, when a controller action has the attribute CreditCheckAttribute. In the ninject kernel, do this,

            kernel.BindFilter(FilterScope.Action, 0).WhenActionMethodHas();

The last step is to put CreditCheckAttribute() against the controller action,

        [CreditCheckAttribute]
        public ActionResult Playlist(TestViewModel model)
        {
            return View(model);
        }

Create a custom HtmlHelper.TextBoxFor() to display label text as “placeholder” integrated with twitter bootstrap

We have TextBoxFor() and LabelFor() in MVC, they are binded with my model. If you are using twitter bootstrap and want to display the model property label as a placeholder in textboxes, what should you do.

Here is what I have done.

My model:

public class User
{
    [Display(Name="User name")]
    public string Username {get;set;}
}

Normally in the view we put,

@model User

<ol>
    <li>
        @Html.LabelFor(p=&gt;p.Username)
        @Html.TextBoxFor(p=&gt;p.Username)
    </li>
</ol>

image

If you want to display it as placeholder in twitter bootstrap,

image

Let’s create a TextBoxPlaceHolderFor() extension method of the htmlhelpers, remember to reference the namespace,

using System.Web.Mvc.Html;

So you can re-use the HtmlHelper.TextBoxFor() in your TextBoxPlaceHolderFor() method.

Here is the code with three overloads,

public static class TextBoxForExtensions
    {
        public static MvcHtmlString TextBoxPlaceHolderFor(this HtmlHelper html, Expression<Func> expression, object htmlAttributes)
        {
            var dict = new RouteValueDictionary(htmlAttributes);
            return html.TextBoxPlaceHolderFor(expression, dict);
        }
        public static MvcHtmlString TextBoxPlaceHolderFor(this HtmlHelper html, Expression<Func> expression)
        {
            var htmlAttributes = new Dictionary();
            return html.TextBoxPlaceHolderFor(expression, htmlAttributes);
        }

        public static MvcHtmlString TextBoxPlaceHolderFor(this HtmlHelper html, Expression<Func> expression, IDictionary htmlAttributes)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
            string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
            string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (!String.IsNullOrEmpty(labelText))
            {
                if (htmlAttributes == null )
                {
                    htmlAttributes = new Dictionary();
                }
                htmlAttributes.Add("placeholder", labelText);
            }
            return html.TextBoxFor(expression, htmlAttributes);
        }

    }

When you call the methods you can do,

@Html.TextBoxPlaceHolderFor(m => m.UserName)

//or 

@Html.TextBoxPlaceHolderFor(m => m.UserName, new { @class="form-field" })


You are done. Now get your form neat and clean using the TextBoxPlaceHolderFor().

My problem with SimpleMembership, MVC4 and AuthorizeAttribute and Roles

I have created an internet application using the MVC4 template with SimpleMembership.

It comes with a “InitializeSimpleMembershipAttribute” which initialize (create) the database if it is not exist. The InitializeSimpleMembershipAttribute attribute is added to the account controller.

Now if I put [Authorize] or [Authroize(Users=”administrator”)] in action method. When I direct to the URL, it gives me an error:

Server Error in ‘/’ Application.

A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: SQL Network Interfaces, error: 26 – Error Locating Server/Instance Specified)

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

SQLExpress database file auto-creation error:

The connection string specifies a local Sql Server Express instance using a database location within the application’s App_Data directory. The provider attempted to automatically create the application services database because the provider determined that the database does not exist. The following configuration requirements are necessary to successfully check for existence of the application services database and automatically create the application services database:

I spent quite a bit of time figuring out this. What I did is, putting the line “WebSecurity.InitializeDatabaseConnection” to the Global.asax file. That ensures the database connection is initialized as early as possible.

WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);

Then I move the filter from AccountController to the FilterConfig.cs file. So when the filters are registered, it calls the InitializeSimpleMembershipAttribute as well.

filters.Add(new InitializeSimpleMembershipAttribute());

I hope this may help people who is having the same problem.

ajax request only action filter, MVC

I created a handy ajax request filter, that you can apply to your ajax action method call.

    public class AjaxRequestAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            if(!filterContext.HttpContext.Request.IsAjaxRequest())
                	//do something
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {

        }
    }
    [AjaxRequest]
    public ActionResult AjaxActionMethod()
    {
        		//to do
    }

Cool features of ASP.NET MVC 4

I have been played around with MVC 4 for a while, now it is finally live.

I found there are quite a few great features included in MVC 4. Here I am going to chat about my favourite ones.

1. Extension to razor scripts

The default view engine supports, HTML attributes starting with ~/ will now be automatically replaced with the application root, developers no longer need to use the Url.Content function.

Another big time saver is conditional attributes. If the @styleClass variable in class="@styleClass" is null, the entirely attribute is automatically removed for you.

2. CSS & JS bundles

a. Instead of listing each css/js file needed by the client, I can use bundle to point to a specific folder.

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                 "~/Scripts/jquery-{version}.js"));

b. You can control the bundling and minification in web.config by turning compilation debug = true or false. Or you can override it from RegisterBundles() method

public static void RegisterBundles(BundleCollection bundles)
{
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                 "~/Scripts/jquery-{version}.js"));

    // Code removed for clarity.
    BundleTable.EnableOptimizations = true;
}

c. bundle is cached on the browser for one year. It sets the http header one year from when the bundle is created.

d. It supports LESS style sheet, by adding “dotless” NuGet package to your project.

implement the IBundleTransform interface,

using System.Web.Optimization;

public class LessTransform : IBundleTransform
{
    public void Process(BundleContext context, BundleResponse response)
    {
        response.Content = dotless.Core.Less.Parse(response.Content);
        response.ContentType = "text/css";
    }
}

and create the bundle of LESS files and CssMinify transform in RegisterBundles() method in BundleConfig.cs file.

var lessBundle = new Bundle("~/My/Less").IncludeDirectory("~/My", "*.less");
lessBundle.Transforms.Add(new LessTransform());
lessBundle.Transforms.Add(new CssMinify());
bundles.Add(lessBundle);

Add it to the view,

 @Styles.Render("~/My/Less");

3. WebAPI

  • Allows the building of HTTP APIs that automatically can serialize to the format requested such as xml/json
  • Support oData so that if the returned object support iQueryable parameters can be added to the return to subsets (paging, top n etc)
  • Allows hosting of WebAPI either in an area web, console, service, windows 8
  • Allows results to easily return custom HTTP response codes and location header for querying data

4. Mobile view engine,

  • Supports adaptive rendering using media queries
  • Allows creation of different mobile specific views
  • Supports mobile templating engines such as jQuery mobile
  • 5. Real time communication

  • Uses SignalR.
  • .NET way of doing what everyone is using node.js for.
  • Allows the server to call JavaScript, e.g pushes a call to the browser thus executing some JavaScript

Validation Summary is not cleared after Ajax.Form successfully posted, MVC 3

My scenario is after the invalid form values are posted through ajax.form, and the validation summary is displaying on the view. After I filled in all the required fields and posted the form with valid values, the post back data is correctly displaying, but the validation summary is remaining on the page.

Here are some code snippets,

The view contains two date picker (textbox) which are required fields.

    @model ClosedTicketByProductReportViewModel
    @using (Ajax.BeginForm("ClosedTicketByProductReport", "Report", new AjaxOptions { UpdateTargetId = "closedTicketByProductGrid", LoadingElementId = "loadingcontainer" }))
    { 
        @Html.ValidationSummary()
                
        <div class="control-group">
            Date From
            <div class="controls">
                @Html.TextBoxFor(p =&gt; p.DateFromForClosedProduct, new Dictionary { { "class", "small" }, { "type", "date" }, { "data-datepicker", "datepicker" } })
            </div>
        </div>
        <div class="control-group">
            Date To
            <div class="controls">
                @Html.TextBoxFor(p =&gt; p.DateToForClosedProduct, new Dictionary { { "class", "small" }, { "type", "date" }, { "data-datepicker", "datepicker" } })
            </div>
        </div>

        Generate Report
        
    }

Here is the view model I created for the view,

    public class ClosedTicketByProductReportViewModel
    {  

        [Required(ErrorMessage = "Please select Date From")]
        public DateTime? DateFromForClosedProduct { get; set; }

        [Required(ErrorMessage = "Please select Date To")]
        public DateTime? DateToForClosedProduct { get; set; }

        //report data
        public List Cases { get; set; }

    }

After I click submit without entering any dates, it displays the error messages as expected.

image

Then I enter the dates, and click submit again. The post back data is retrieved from controller action, but the validation summary remains on the page. As you can see the validation errors on the textbox itself are gone. Ajax.Form is only lack of support on the validation summaries.

image

Here is my fix, I don’t think it is great, but it is a work around.

    @using (Ajax.BeginForm("ClosedTicketByProductReport", "Report", new AjaxOptions { OnSuccess = "HideValidationSummary", UpdateTargetId = "closedTicketByProductGrid", LoadingElementId = "loadingcontainer" }))
    { }

I created a javascript function and call it on OnSuccess = "HideValidationSummary, the javascript function just hide the validation summary div.

function HideValidationSummary() {
    $(".validation-summary-errors").hide();
}

If anyone come across my blog post and has a better solution, please leave me a message. Much appreciated.

Using twitter bootstrap with bottom sticky footer

Nowadays, more website comprises 3 components. header, body and footer. With bootstrap, you can get it done even quicker. Here I created a sample start up project with most common layout in the world: Top fixed header, body and bottom sticked footer.

Here are some codes of the _layout.cshtml (if you are using MVC) or masterpages (asp.net) or any other languages whatever you call it.

For header section, use the bootstrap default “navbar” classes, you can set it to “navbar-fixed-top” or not, it is your choice. Set to top fixed, the header bar is always visible when you scrolling down.

    <div class="navbar navbar-fixed-top">
        <div class="navbar-inner">
            <div class="container">
                <a href="/" class="brand">Brand name</a>
                <ul class="nav">
                    <li><a href="">Home</a></li>
                    <li class="divider-vertical"></li>  
                    <li><a href="">Products</a></li>
                    <li class="divider-vertical"></li>
                    <li><a href="">FAQ</a></li>
                </ul>
                <ul class="nav pull-right">
                    <li><a href="">Register</a></li>
                    <li><a href="">Login</a></li>
                </ul>
            </div>
        </div>
    </div>

For body section, I created a “wrap” div outside the “main” div, this is to make sure the footer stays after it. You will know what I mean after I show you the CSS

    <div id="wrap">
    <div id="main" class="container clear-top">
        <div class="row"> 
            <div class="span12">
                @RenderBody()        
            </div>
        </div>
    </div>
    </div>

For footer section, using <footer> is completely ok with modernizer, and I created a 3 columns with same size for your footer navigation links.

    <footer class="footer" style="background-color:#c2c2c2;">
        <div class="container" style="margin:0 auto;">
            <div class="row">
                <div class="span2 offset2">
                    <h4>Company</h4>
                    <ul>
                        <li><a href="">About Us</a></li>
                        <li><a href="">Contact</a></li>
                    </ul>
                </div>
                <div class="span2 offset1">
                    <h4>Resource</h4>
                    <ul>
                        <li><a href="">Get help</a></li>
                        <li><a href="">FAQ</a></li>
                        <li><a href="">Support</a></li>
                    </ul>
                </div>
                <div class="span2 offset1">
                    <h4>Connect</h4>
                    <ul>
                        <li><a href="" title="Facebook">Facebook</a></li>
                        <li><a href="" title="Twitter">Twitter</a></li>
                        <li><a href="" title="LinkedIn">LinkedIn</a></li>
                        <li><a href="" title="Google+">Google+</a></li>
                        <li><a href="" title="RSS">RSS</a></li>
                        <li><a href="" title="Blogs">Blogs</a></li>
                    </ul>
                </div>
            </div>
        </div>
    </footer>

Now let’s see the CSS,

/* styles for layout */
html,body
{
    height:100%;
}

#wrap
{
  min-height: 100%;  
}

#main
{
    overflow:auto;
    padding-bottom:150px; /* this needs to be bigger than footer height*/
}

.footer 
{
    position: relative;
	margin-top: -150px; /* negative value of footer height */
	height: 150px;
	clear:both;
    padding-top:20px;
    color:#fff;
} 

Your footer is sticked to the bottom always.

To download the project sample, go to codeplex

http://bootstrapfooter.codeplex.com/

Security configurations for MVC application & IIS 7

After Sony had been hacked earlier this year, they start taking security really serious now.

And recently I have been involved in a few Sony promotion projects that makes me learn a lot about how to make a website more securer from both web server configurations and application itself.

Go through a 50 pages configuration benchmark book is a pain, but there are some really simple steps people easily forget. Here are some highlights:

  1. Make sure web content is on non-system partition.
  2. Remove or rename well-known urls.
    %systemdrive%\inetpub\AdminScripts
    %systemdrive%\inetpub\scripts\IISSamples
    http://localhost/iissamples
    http://localhost/iishelp
    http://localhost/printers
    http://localhost/iisadmpwd
  3. Require a host headers on all sites. Don’t bind http:/*:80 to any site.
  4. Disable directory browsing
  5. Set default application pool identity to least privilege principal.
  6. Ensure application pools run under unique identities, and unique application pools for different sites.
  7. Config anonymous user identity to use application pool identity, this will greatly reduce the number of accounts needed for websites.
    open applicationHost.config and make sure you set the userName attribute of the anonymousAuthentication tag is set to a blank string.

    <system.webServer><security><authentication><anonymousAuthentication userName = ""/></authentication></security></system.webServer>
  8. Configure authentications,

    a. Ensure sensitive site features is restricted to authenticated principals only.

    <system.webServer><security><authorization><remove users="*" roles="" verbs="" /><add accessType="Allow" roles="administrators" />
    
    </authorization></security></system.webServer></configuration>

    b. Require SSL in forms authentications and configure forms authentication to use cookies.

    <pre><system.web><authentication><forms cookieless="UseCookies" requireSSL="true" /></authentication></system.web></pre>

    c. Configure cookie protection mode for forms authentication.

    <pre><system.web><authentication><forms cookieless="UseCookies" protection="All" /></authentication></system.web></pre>

    d. Never save password in clear format!!

  9. Asp.net configurations.

    a. Set deployment method to retail, modify machine.config

    <system.web>  <deployment retail="true" /></system.web>

    b. Turn debug off.

    <system.web><compilation debug="false" /></system.web></configuration>

    c. Ensure custom error messages are not off.

    <customErrors mode="RemoteOnly"/> or <customErrors mode = "On"/>

    d. Ensure failed request tracing is not enabled.

    – Open IIS.

    – Go to Connections pane, select server connection, site, application or directory.

    – In actions pane, click failed request tracing… make sure the checkbox is not checked.

    e. Configure to use cookies mode for session state in web.config

    <system.web><sessionState cookieless="UseCookies" /></system.web>

    f. Ensure cookies are set with HttpOnly attribute in web.config. This will stop client side script access to cookies.

    <configuration><system.web><httpCookies httpOnlyCookies="true" /></system.web></configuration>

    g. Set global .NET trust level. Open IIS, in the features view, double click .NET Trust Levels.

  10. Request filtering & restrictions in web.config, set maxAllowedContentLength, maxUrl, maxQueryStringallowHighBitCharacters (setting to dis-allow non-ASCII characters) & allowDoubleEscaping.
    <system.webServer><security><requestFiltering allowHighBitCharacters="false" allowDoubleEscaping = "false"><requestLimits maxAllowedContentLength="30000000" maxUrl="4096" maxQueryString="1024" /></requestFiltering></security></system.webServer></configuration>
  11. Disallow unlisted file extensions in web.config.
    <system.webServer><security><requestFiltering><fileExtensions allowUnlisted="false" ><add fileExtension=".asp" allowed="true"/>
    
    <add fileExtension=".aspx" allowed="true"/><add fileExtension=".html" allowed="true"/></fileExtensions></requestFiltering></security></system.webServer></configuration>