Friday, August 17, 2012

Upgrading from ASP.NET MVC 3 + Web API RC to ASP.NET MVC 4 to Web API RTM

ASP.NET MVC 4 and Web API are finally released! On to upgrading my projects to the latest version.

I followed the instructions at Upgrading an ASP.NET MVC 3 Project to ASP.NET MVC 4 to convert ASP.NET MVC 3 to MVC 4. However, I've got lots of errors. It looks like Microsoft added DisplayNameExtensions.DisplayNameFor, thus conflicting with my own DisplayNameFor extension that does the same thing. LabelExtensions.LabelFor(this HtmlHelper html, Expression> expression, object htmlAttributes) is added too!

 Also, false (boolean) and null (string) now acquired special meanings when used as values in HTML attributes. They are used in Conditional Attributes. False and null thus do not output any attribute. Previously, false gets rendered as the string "False" but now it's gone! My model validator thus complains that the attribute value is not provided. To restore the old behaviour, just do a boolean.ToString().

Using NuGet, I upgraded from Web API RC to Web API RTM. However, for me, my project won't compile. QueryableAttribute is missing! Turns out that I've to add the new package Microsoft.AspNet.WebApi.OData to get the attribute back. Being a little risk adverse because the package is in alpha, I chose to remove all references to QueryableAttribute instead.

Wednesday, August 15, 2012

Windows 8 IndexedDB invalid access error

While following the code at Getting Started with IndexedDB, I encountered the invalid access error at this line
var transaction = db.transaction("people", IDBTransaction.READ_WRITE);

It turns out that the IDBTransaction is obsolete. Following the instruction at IndexedDB changed in IE10 PP6, I changed the code to the following. It now works!
var transaction = db.transaction("people", "readwrite");

Wednesday, July 11, 2012

Getting FormsAuthentication to work inside iOS WebView

I developed a Facebook mobile web application and wanted users to easily access it by scanning a QR Code. However, while the web application works perfectly in Mobile Safari, it totally fails in the QR Code reader app and gets stuck in the landing page. First suspicion was Facebook’s login process. After extensive debugging, I could not nail the problem, as the code runs in the exact sequence as I wanted it to.

The only thing not behaving correctly is that this.User.Identity.IsAuthenticated always returns false. The other abnormally is that iOS WebView sends a user agent that does not contain the word Safari. Interestingly, the this.Request.Cookies collection contains the forms authentication cookie. That finally brought me to conclude that the problem lies in FormsAuthentication not recognizing the cookie. Luckily I found the answer on http://stackoverflow.com/questions/3605076/thread-currentprincipal-claims-incorrectly-to-be-anynomous.

By setting

<authentication mode="Forms">
  <forms cookieless="UseCookies" />
</authentication>

the web application works perfectly inside WebView. Hurray!

Scotts Hanselman recently blogged about the very same problem and offered another way to solve the problem http://www.hanselman.com/blog/FormsAuthenticationOnASPNETSitesWithTheGoogleChromeBrowserOnIOS.aspx. And the good news is that things will work out of the box starting from ASP.NET 4.5. Good job Scott!

Tuesday, May 29, 2012

WebsitePanel MySQL error

While setting up WebsitePanel, the MySQL setup page always gives the following error.

Could not load file or assembly 'MySql.Data, Version=6.3.7.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d' or one of its dependencies

By adding the following to the web.config of C:\WebsitePanel\Server, the problem is resolved.

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="MySql.Data" publicKeyToken="c5687fc88969c44d" culture="neutral" />
      <bindingRedirect oldVersion="6.3.7.0-6.5.4.0" newVersion="6.5.4.0" />
    </dependentAssembly>
  </assemblyBinding>
</runtime>

Thursday, May 03, 2012

Forcing ValidationSummary to include property errors from IValidatableObject

I implemented IValidatableObject to my class and intended to have the errors shown in @Html.ValidationSummary(true). However, the class with the IValidatableObject  is a property of the model in my view, and that causes the errors to be hidden when I exclude property errors.

There is another person on Stackoverflow with exactly the same problem, but no one has answer. http://stackoverflow.com/questions/6433023/mvc3-validationsummary-exclude-property-errors-ivalidatableobject/10433504.

To solve this, I dug out MVC3’s source code, copied and changed the ValidationSummary method to allow specific properties to be included. The following shows how to use my ValidationSummary method

@Html.ValidationSummary(new [] { "PropertyName" })

would include the property named PropertyName

@Html.ValidationSummary(new [] { "ArrayName[]" })

would include the properties ArrayName[0], ArrayName[1] etc.

@Html.ValidationSummary(new [] { "ArrayName[]", "PropertyName" })

would include both.

And here’s the code!

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors)

{

    return ValidationSummary(htmlHelper, includePropertyErrors, null, null);

}

 

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors, string message)

{

    return ValidationSummary(htmlHelper, includePropertyErrors, message, null);

}

 

public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, string[] includePropertyErrors, string message, IDictionary<string, object> htmlAttributes)

{

    if (htmlHelper == null)

    {

        throw new ArgumentNullException("htmlHelper");

    }

 

    FormContext formContext = htmlHelper.ViewContext.ClientValidationEnabled ? htmlHelper.ViewContext.FormContext : null;

    if (htmlHelper.ViewData.ModelState.IsValid)

    {

        if (formContext == null)

        {  // No client side validation

            return null;

        }

 

        // TODO: This isn't really about unobtrusive; can we fix up non-unobtrusive to get rid of this, too?

        if (htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)

        {  // No client-side updates

            return null;

        }

    }

 

    string messageSpan;

    if (!string.IsNullOrEmpty(message))

    {

        TagBuilder spanTag = new TagBuilder("span");

        spanTag.SetInnerText(message);

        messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;

    }

    else

    {

        messageSpan = null;

    }

 

    StringBuilder htmlSummary = new StringBuilder();

    TagBuilder unorderedList = new TagBuilder("ul");

 

    IEnumerable<ModelState> modelStates = from ms in htmlHelper.ViewData.ModelState

                                            where ms.Key == htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix ||

                                                includePropertyErrors.Any(property =>

                                                {

                                                    string prefixedProperty = string.IsNullOrEmpty(htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix) ? property : htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix + "." + property;

                                                    if (property.EndsWith("[]"))

                                                    {

                                                        return prefixedProperty.Substring(0, property.Length - 2) == Regex.Replace(ms.Key, @"\[[^\]]+\]", string.Empty);

                                                    }

                                                    else

                                                    {

                                                        return property == ms.Key;

                                                    }

                                                })

                                            select ms.Value;

 

    if (modelStates != null)

    {

        foreach (ModelState modelState in modelStates)

        {

            foreach (ModelError modelError in modelState.Errors)

            {

                string errorText = GetUserErrorMessageOrDefault(htmlHelper.ViewContext.HttpContext, modelError);

                if (!String.IsNullOrEmpty(errorText))

                {

                    TagBuilder listItem = new TagBuilder("li");

                    listItem.SetInnerText(errorText);

                    htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));

                }

            }

        }

    }

 

    if (htmlSummary.Length == 0)

    {

        htmlSummary.AppendLine(@"<li style=""display:none""></li>");

    }

 

    unorderedList.InnerHtml = htmlSummary.ToString();

 

    TagBuilder divBuilder = new TagBuilder("div");

    divBuilder.MergeAttributes(htmlAttributes);

    divBuilder.AddCssClass((htmlHelper.ViewData.ModelState.IsValid) ? HtmlHelper.ValidationSummaryValidCssClassName : HtmlHelper.ValidationSummaryCssClassName);

    divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);

 

    if (formContext != null)

    {

        if (!htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled)

        {

            // client val summaries need an ID

            divBuilder.GenerateId("validationSummary");

            formContext.ValidationSummaryId = divBuilder.Attributes["id"];

            formContext.ReplaceValidationSummary = false;

        }

    }

 

    return new MvcHtmlString(divBuilder.ToString(TagRenderMode.Normal));

}

 

private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error)

{

    return string.IsNullOrEmpty(error.ErrorMessage) ? null : error.ErrorMessage;

}