Friday, August 12, 2016

Configure Theano to use Intel MKL on Windows

In order to use convolution layers and FFT in Theano, Theano requires either BLAS or CUDA to be present. On computers without an Nvidia graphics card, BLAS is the only choice.

There are 3 popular BLAS implementations, namely Intel MKL, OpenBLAS and ATLAS. A benchmark shows that ATLAS is the slowest, while MKL and OpenBLAS are on par. I could not get OpenBLAS to work, as I keep getting the error "DLL load failed: The specified module could not be found" despite ensuring that all the dependent DLLs are on my path.

Getting the MKL library

  1. Before Intel would let you download the MKL library, you have to register at https://software.intel.com/en-us/intel-mkl
  2. You will receive an email containing the link to the download page of various Intel libraries. Download the Math Kernel Library (MKL) installer
  3. Install

 Configuration

  1. By default, MKL gets installed into C:\Program Files (x86)\IntelSWTools. I could not figure out what escape sequence I need to enter to get spaces to work in gcc. Thus use Link Shell Extension to create a symbolic link from C:\tools\IntelSWTools to C:\Program Files (x86)\IntelSWTools.
  2. Edit C:\Users\.theanorc and add the following
    [blas]
    ldflags = -LC:/tools/IntelSWTools/compilers_and_libraries/windows/mkl/lib/intel64_win -LC:/tools/IntelSWTools/compilers_and_libraries/windows/mkl/lib/intel64_win_mic  -lmkl_core -lmkl_intel_thread -lmkl_lapack95_lp64 -lmkl_blas95_lp64 -lmkl_rt
  3. Add C:\tools\IntelSWTools\compilers_and_libraries\windows\redist\intel64_win\mkl to your system environmental variable PATH

Checking that it works

  1. Open command line and cd to \misc\
  2. python.exe check_blas.py
  3. The output should have something like the following
    We executed 10 calls to gemm with a and b matrices of shapes (5000, 5000) and (5000, 5000).

    Total execution time: 18.00s on CPU (with direct Theano binding to blas).

    Try to run this script a few times. Experience shows that the first time is not as fast as followings calls. The difference is not big, but consistent.

Sunday, August 07, 2016

Compiling aspell-python

While the Unofficial Windows Binaries for Python has aspell-python for Python 3.4, it does not have one for Python 3.5. Thus, we've got to compile it ourselves.

Since Python 3.5 requires Visual Studio 2015, we first have to modify C:\tools\python\tools\Lib\distutils\cygwinccompiler.py to recognize Visual Studio 2015
--- cygwinccompiler.py
+++ cygwinccompiler.py
@@ -82,7 +82,18 @@ def get_msvcr():
         elif msc_ver == '1600':
             # VS2010 / MSVC 10.0
             return ['msvcr100']
+        elif msc_ver == '1700':
+            # Visual Studio 2012 / Visual C++ 11.0
+            return ['msvcr110']
+        elif msc_ver == '1800':
+            # Visual Studio 2013 / Visual C++ 12.0
+            return ['msvcr120']
+        elif msc_ver == '1900':
+            # Visual Studio 2015 / Visual C++ 14.0
+            # "msvcr140.dll no longer exists" http://blogs.msdn.com/b/vcblog/archive/2014/06/03/visual-studio-14-ctp.aspx
+            return ['vcruntime140']
         else:
+            # to do: can we make this futureproof?
             raise ValueError("Unknown MS Compiler version %s " % msc_ver)
 



Next, download and unzip aspell-python from https://github.com/WojciechMula/aspell-python

 In setup.3.py, modify get_include_dirs() from
        return [] 
to
        return ['c:/tools/Aspell/include'] 
this is for aspell-python build scripts to find the aspell.h

In  aspell.c, modify static PyTypeObject aspell_AspellType from
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
to
    PyVarObject_HEAD_INIT(NULL, 0)

and insert after
     speller_as_sequence.sq_contains = m_contains;
the following
    Py_TYPE(&aspell_AspellType) = &PyType_Type;
as per instructions on https://docs.python.org/3.5/c-api/typeobj.html#c.PyObject.ob_type

Copy C:\tools\Aspell\lib\libaspell.dll.a to the directory with aspell-python source code, and rename it to aspell.lib.

Then build it normally using
python setup.3.py build
python setup.3.py install

Friday, January 22, 2016

Compiling Aspell on Windows

Download Aspell

Download and extract GNU Aspell 0.60.6.1 from http://aspell.net/
Download and extract the English dictionary (aspell6-en-2016.01.19-0.tar.bz2)

Install Chocolatey machine package manager

Start PowerShell as Administrator
Enter the following commands:
Set-ExecutionPolicy ByPass
iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))

Install the build tools

Start cmd as Administrator
Enter the following commands:
choco install msys2
choco install mingw
choco install activeperl

choco install linkshellextension

Start msys2_shell.bat in C:\tools\msys2
Enter the following commands:

pacman -S make
pacman -S diffutils


Update build script


Overwrite config.guess with file from http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
Overwrite config.sub with file from http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD

Build

In msys2 shell, navigate to downloaded Aspell
Enter:
./configure CXXFLAGS='-O2' --enable-win32-relocatable --disable-rpath --prefix="C:/tools/Aspell" --enable-data-dir="C:/tools/Aspell/data" --enable-dict-dir="C:/tools/Aspell/data"

In the downloaded Aspell, add the file gen/dirs.h with the following contents
#define PREFIX "C:/tools/Aspell"
#define DATA_DIR "C:/tools/Aspell/data"
#define DICT_DIR DATA_DIR
#define CONF_DIR PREFIX


Add a line to common/file_util.cpp under the “#ifdef WIN32” section
#include "asc_ctype.hpp"

In msys2 shell, enter
make
make install

Add C:\tools\Aspell\bin to PATH system environment variable

Create hardlink from C:\tools\Aspell\data (drop as hardlink) to C:\tools\Aspell\lib\aspell-0.60 (pick link source)

Restart msys2 shell and navigate to downloaded Aspell dictionary and enter
./configure
make
make install

Final steps

Open cmd, navigate to c:\tools\Aspell\bin and enter
aspell dump config > ../aspell.conf
Open c:\tools\Aspell\aspell.conf in Notepad and add
encoding utf-8
below # default: !encoding = none

Add
sug-mode bad-spellers
below
# sug-mode (string)
#   suggestion mode
# default: normal

To use in Aspell in Python

Download aspell-python from http://www.lfd.uci.edu/~gohlke/pythonlibs/
In cmd, navigate to c:\tools\python\Scripts and enter
pip install [path to the file aspell-python.whl]

Create symbolic link from C:\ProgramData\Aspell to  C:\tools\Aspell
Create symbolic link from C:\tools\Aspell\Dictionaries\en_US to C:\tools\Aspell\data

Start Python
import aspell
spell = aspell.Speller()
enc = spell.ConfigKeys()['encoding'][1]
print(spell.suggest('mispelling'.encode(enc)))


Credits:
http://nosq.com/2005/07/aspell-0603-for-win32/
https://github.com/WojciechMula/aspell-python/blob/master/windows.rst

Wednesday, March 18, 2015

Creating a constraint to ensure intersection table only joins certain records

I have a database with the following tables

I need to ensure that users only becomes friends with other users of the same department.

Approach 1 – DepartmentId on Friend

On http://stackoverflow.com/a/20443626/263003, diarmuid suggested to add the DepartmentId on the Friend table and use the foreign key constraints of (MyselfId, AppId) and (FriendId, AppId). However, I would have to set the primary key of the User table as (Id, DepartmentId) – this creates a super key and I want to avoid that.

Approach 2 – CHECK CONSTRAINT on VIEW

The simplest approach I could think of is to create a VIEW joining User to Friend to User, and add a CHECK CONSTRAINT on the view. However, CHECK CONSTRAINT only applies on TABLE and not VIEW.

Approach 3 – CROSS JOIN and UNIQUE INDEX on VIEW

On http://stackoverflow.com/a/9751172/263003, Martin Smith uses the approach of selecting the records we do not want, creating duplicate records of those using CROSS JOIN, and ensuring those records cannot exists by using a UNIQUE INDEX. Let’s start by creating the INDEXED VIEW

CREATE VIEW [dbo].[CheckFriendView]
WITH SCHEMABINDING
AS
SELECT myself.Id AS MyselfId, myFriend.Id AS MyFriendId, myself.DepartmentId AS MyDepartmentId, myFriend.DepartmentId AS FriendDepartmentId
FROM  dbo.[User] AS myself
INNER JOIN  dbo.Friend
ON dbo.Friend.MyselfId = myself.Id
INNER JOIN dbo.[User] AS myFriend
ON dbo.Friend.FriendId = myFriend.Id
WHERE myself.DepartmentId <> myFriend.DepartmentId

GO

CREATE UNIQUE CLUSTERED INDEX IDX_CheckFriendView
   ON [dbo].[CheckFriendView] (MyselfId, MyFriendId)

GO

However, I got the error

Cannot create index on view "Demo.dbo.CheckFriendView". The view contains a self join on "Demo.dbo.User".

Let’s avoid self join by moving the join to a WHERE clause

CREATE VIEW [dbo].[CheckFriendView]
WITH SCHEMABINDING
AS
SELECT MyselfId, FriendId
FROM dbo.Friend AS f
WHERE EXISTS (
    SELECT 1
    FROM  dbo.[User] AS myself
    INNER JOIN dbo.Friend
    ON dbo.Friend.MyselfId = myself.Id
    INNER JOIN dbo.[User] AS myFriend
    ON dbo.Friend.FriendId = myFriend.Id
    WHERE myself.Id = f.MyselfId AND
          myFriend.Id = f.FriendId AND
          myself.DepartmentId <> myFriend.DepartmentId
)

GO

CREATE UNIQUE CLUSTERED INDEX IDX_CheckFriendView
   ON [dbo].[CheckFriendView] (MyselfId, FriendId)

GO

Another error comes up

Cannot create index on view "Demo.dbo.CheckFriendView" because it contains one or more subqueries. Consider changing the view to use only joins instead of subqueries. Alternatively, consider not indexing this view.

Looks like SQL Server will never let us join.

Approach 4 – Turning MyselfId and FriendId into a single column

Jonathan avoided the self join by combining the MyselfId and FriendId columns with the help of a numbers table containing 2 entries, then joining into the User table – http://jmkehayias.blogspot.sg/2008/12/creating-indexed-view-with-self-join.html

Let’s start by combining MyselfId and FriendId

CREATE VIEW [dbo].[CheckFriendOneColumnView]
WITH SCHEMABINDING
AS
SELECT MyselfId, CASE WHEN n.Number = 1 THEN MyselfId ELSE FriendId END AS MyselfOrFriendId
FROM dbo.Friend AS f
CROSS JOIN dbo.[Number] AS n

And then add the joining of User

INNER JOIN dbo.[User] AS u
ON f.UserId = u.Id OR f.FriendId = u.Id

Oh wait a minute, this is getting unnecessarily complicated. Could we just use

INNER JOIN dbo.[User] AS u
ON f.UserId = u.Id OR f.FriendId = u.Id

without the cross join?

Finally

We do an INNER JOIN with OR condition. Each row in Friend will become 2 rows, one with the DepartmentId for myself, the other with the DepartmentId for my friend.

Then aggregate the (MyselfId, DepartmentId). Since all the friends should belong to the same department, this aggregation should leave MyselfId as unique.

Lastly add the UNIQUE INDEX on MyselfId.

CREATE VIEW [dbo].[CheckFriendView]
WITH SCHEMABINDING
AS
SELECT f.MyselfId, u.DepartmentId, COUNT_BIG(*) AS BigCount
FROM dbo.Friend AS f
INNER JOIN dbo.[User] AS u
ON f.MyselfId = u.Id OR f.FriendId = u.Id
GROUP BY f.MyselfId, u.DepartmentId

GO

CREATE UNIQUE CLUSTERED INDEX IDX_CheckFriendView
    ON [dbo].[CheckFriendView] (MyselfId)

GO

Note: I had to add COUNT_BIG(*) AS BigCount due to another error

Cannot create index on view 'CheckFriendView' because its select list does not include  a proper use of COUNT_BIG. Consider adding COUNT_BIG(*) to select list.

which basically says I must add it!

Update:

Warning: while the INNER JOIN ON a OR b works, it results in very inefficient query (no JOIN predicate) and slows down every changes in the database!

Saturday, February 28, 2015

React JS in ASP.NET MVC part 2—setting up React

Set up the project and libraries for React

Let’s start by creating a new ASP.NET MVC project

Next, we will use NuGet to download the React libraries that we need

Right-click on References and click Manage NuGet Packages

Search for react.js

Install these libraries:

  • react.js
    This contains the JavaScript for the browser
  • ReactJS.NET (MVC 4 and 5)
    This contains the libraries necessary for server-side rendering
  • ReactJS.NET – JSX for ASP.NET Web Optimization Framework
    This contains the library for transforming JSX into plain JavaScript, and then minified and bundled to speed up download
  • ReactJS.NET – ClearScript V8
    V8 is a faster JavaScript engine which we will use to run our React scripts server-side. This is much better than the Jint engine bundled with ReactJS.NET (MVC 4 and 5)

Next, we prepare the minification and bundling for the React JavaScript library.

Include the React JavaScript in our App_Start\BundleConfig.cs

// React JS
// Should not use the official React CDN at //fb.me/, as it redirects to HTTP and breaks HTTPS sites.
bundles.Add(new ScriptBundle("~/js/reactjs", "//cdnjs.cloudflare.com/ajax/libs/react/0.12.2/react-with-addons.min.js")
{
     CdnFallbackExpression = "window.React" }.Include(
     "~/Scripts/react/react-with-addons-{version}.min.js" ));

Include the ~/js/reactjs script bundle in Views\Shared\_Layout.cshtml

<head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>@ViewBag.Title - My ASP.NET Application</title>
     @Styles.Render("~/Content/css")
     @Scripts.Render("~/bundles/modernizr")
     @Scripts.Render("~/js/reactjs") </head>

Next, we speed up server-side rendering by disabling global members in V8

Replace the jsEngineSwitcher sectionGroup with the following

    <sectionGroup name="jsEngineSwitcher">
      <section name="core" type="JavaScriptEngineSwitcher.Core.Configuration.CoreConfiguration, JavaScriptEngineSwitcher.Core" />
      <section name="msie" type="JavaScriptEngineSwitcher.Msie.Configuration.MsieConfiguration, JavaScriptEngineSwitcher.Msie" />
      <section name="jint" type="JavaScriptEngineSwitcher.Jint.Configuration.JintConfiguration, JavaScriptEngineSwitcher.Jint" />
      <section name="v8" type="JavaScriptEngineSwitcher.V8.Configuration.V8Configuration, JavaScriptEngineSwitcher.V8" />
    </sectionGroup>

Replace the jsEngineSwitcher section in Web.config with the following

  <jsEngineSwitcher xmlns="http://tempuri.org/JavaScriptEngineSwitcher.Configuration.xsd">
    <core>
      <engines>
        <add name="V8JsEngine" type="JavaScriptEngineSwitcher.V8.V8JsEngine, JavaScriptEngineSwitcher.V8" />
        <add name="MsieJsEngine" type="JavaScriptEngineSwitcher.Msie.MsieJsEngine, JavaScriptEngineSwitcher.Msie" />
        <add name="JintJsEngine" type="JavaScriptEngineSwitcher.Jint.JintJsEngine, JavaScriptEngineSwitcher.Jint" />
      </engines>
    </core>
    <v8 enableDebugging="false" debugPort="9222" disableGlobalMembers="true" maxNewSpaceSize="0" maxOldSpaceSize="0" maxExecutableSize="0" />
  </jsEngineSwitcher>

We are ready to start writing our first Hello World in React!

Create the folder Scripts/Home and add a JavaScript File named Index.jsx and paste the following

var HomeIndex = React.createClass({
    render: function() {
        return (<div>Hello world!</div>);
} });

We will also bundle this JSX file in App_Start\BundleConfig.cs

bundles.Add(new JsxBundle("~/js/home/index").Include(
     "~/Scripts/Home/Index.jsx" ));

Next, we need to include the JSX for server-side rendering, by adding the following in App_Start\ReactConfig.cs

// If you want to use server-side rendering of React components, 
// add all the necessary JavaScript files here. This includes 
// your components as well as all of their dependencies.
// See http://reactjs.net/ for more information. Example:
ReactSiteConfiguration.Configuration
     .AddScript("~/Scripts/Home/Index.jsx");

Replace the contents in Views\Home\Index.cshtml with the following

@{
     ViewBag.Title = "Home Page"; } @Html.React("HomeIndex", new { }, containerId: "homeIndex") @Scripts.Render("~/js/home/index") @Html.ReactInitJavaScript()

Run our application and we have our Hello World!

Next up—making our grid view

Thursday, February 26, 2015

React JS in ASP.NET MVC part 1—why Isomorphic JavaScript?

The conflict of rich web application and search engine optimization

Be it Gmail, Facebook or Github, we have come to expect interactivity without having to wait for a full page reload. Specifically, these web applications are single-page applications (SPA), where new content arising from interactions are sent and retrieved via AJAX.
There are many frameworks to make developing SPA easier. AngularJS, EmberJS and KnockoutJS comes to mind. However, these traditional frameworks only address one part of the equation—the browser side. The server side spits out the HTML template and data separately, causing search engines not to be able to index the page properly, for the HTML template contains no information worthy of mention in a search query. There is thus the need for additional effort to develop a classic version of the SPA in order to optimize for search engine.
Can we cut the additional effort and run our browser side SPA code on the server side too? That would generate the classic version straight away!

React comes to the rescue

React does exactly that!
React, Meteor and several other Isomorphic JavaScript frameworks arise to make it easy to have rich single-page applications, while generating SEO friendly pages. What’s more, the initial blank or placeholder page that appears before data is loaded is gone, since the template with data is already rendered at the server side.
With some additional care to place HTML forms and submit buttons and intercepting them to do AJAX, the rich web application can still function even with JavaScript turn off. Talk about progressive enhancement and graceful degradation!

In part 2, set up the React libraries

Saturday, February 07, 2015

Get an extra 5GB of storage for Copy when you use this link!

My 25 GB of Dropbox space earned from Space Race is expiring in a month! I'll be left with a meagre 2.5 GB only. ::sob::

Luckily, there's a Dropbox competitor, Copy, offering 15 GB of base storage. What's more, if you use the referral link below, both you and I will get an additional 5GB! That's a lot, especially when Dropbox only gives 10% of that per referral.

Get your 20GB NOW
https://copy.com?r=QPkByG

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;

}