Friday, August 17, 2012
Upgrading from ASP.NET MVC 3 + Web API RC to ASP.NET MVC 4 to Web API RTM
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
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
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
");
Tuesday, August 07, 2012
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!
publicstaticMvcHtmlString ValidationSummary(thisHtmlHelper htmlHelper, string[] includePropertyErrors)
{
return ValidationSummary(htmlHelper, includePropertyErrors, null, null);
}
publicstaticMvcHtmlString ValidationSummary(thisHtmlHelper htmlHelper, string[] includePropertyErrors, string message)
{
return ValidationSummary(htmlHelper, includePropertyErrors, message, null);
}
publicstaticMvcHtmlString ValidationSummary(thisHtmlHelper htmlHelper, string[] includePropertyErrors, string message, IDictionary<string, object> htmlAttributes)
{
if (htmlHelper ==null)
{
thrownewArgumentNullException("htmlHelper");
}
FormContext formContext = htmlHelper.ViewContext.ClientValidationEnabled ? htmlHelper.ViewContext.FormContext : null;
if (htmlHelper.ViewData.ModelState.IsValid)
{
if (formContext ==null)
{ // No client side validation
returnnull;
}
// 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
returnnull;
}
}
string messageSpan;
if (!string.IsNullOrEmpty(message))
{
TagBuilder spanTag =newTagBuilder("span");
spanTag.SetInnerText(message);
messageSpan = spanTag.ToString(TagRenderMode.Normal) +Environment.NewLine;
}
else
{
messageSpan =null;
}
StringBuilder htmlSummary =newStringBuilder();
TagBuilder unorderedList =newTagBuilder("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 =newTagBuilder("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 =newTagBuilder("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;
}
}
returnnewMvcHtmlString(divBuilder.ToString(TagRenderMode.Normal));
}
privatestaticstring GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error)
{
returnstring.IsNullOrEmpty(error.ErrorMessage) ?null : error.ErrorMessage;
}