Laravel User Authentication with Ajax Validation

This post documents how to add Ajax form validation to Laravel User Authentication that is generated by the Artisan console.

Requirements

An instance of Laravel 5.2 setup for local development is required. Head over to the Laravel 5.2 docs to get setup as needed. For my setup on Windows 10, I am using XAMPP 5.6.12 which meets all of the requirements. Note that your document root will need to be the laravel/public directory. Here is the virtual hosts configuration I am using for Laravel.

httpd-vhosts.conf
<VirtualHost *:8080>
    DocumentRoot "C:/xampp/htdocs/laravel/public"
    ServerName laravel.dev
    ServerAlias www.laravel.dev
    SetEnv APPLICATION_ENV development
    <Directory "C:/xampp/htdocs/laravel">
        Options Indexes MultiViews FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

More info on setting up XAMPP for Windows

New Laravel Site

Using Composer, install Laravel 5.2 into htdocs/laravel

cd c:/xampp/htdocs

composer create-project laravel/laravel laravel "5.2.*"
Generate Authentication Scaffolding
cd laravel

php artisan make:auth

Now the Laravel site has routes and views to allow a user to register, login, logout, and reset their password. A HomeController has also been added to allow authenticated users to enter its view.

User Model

I prefer to organize models within their own directory. Therfore, let’s create a folder named Models in the app directory and move the User model into it.

mkdir app/Models

mv app/User.php app/Models

Edit app/Models/User.php, update the App namespace to App\Models.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    ...

  • Throughout this tutorial you will encounter an ellipsis … in the code examples. These are not a part of the code and are there only to denote code that is being skipped and not applicable to the example. To view the entire file, examine the source code.

Edit app/Http/Controllers/Auth/AuthController.php, update use App\User to use App\Models\User.

AuthController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Models\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    ...

Edit, config/auth.php, update the authentication drivers user provider.

'users' => [
    'driver' => 'eloquent',
    'model' => App\User::class,
],
REPLACE
'model' => App\User::class,
WITH
'model' => App\Models\User::class,

Database

Edit the .env configuration file to access the database. I have already created a database named laravel on the MySQL Server that is part of XAMPP.

.env
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

The settings above are the only ones I needed to update for my setup, I do not have a password set for MySQL Server and will just use the root account for local development. Now run php artisan migrate to create the tables for users.

Run Migration
php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table

Validation Rules

The validation rules are currently located in the AuthController, I would like to be able to share them with the new controller we will create later for ajax validation. Since these rules apply to the User, and that model is already being used by the AuthController, it seeems to be the best place for them.

Edit app/Models/User.php, adding the public function rules() to the class.

User.php
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    /**
     * Get the validation rules that apply to the user.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:users',
            'password' => 'required|min:6|confirmed',
        ];
    }

    ...

Edit app/Http/Controllers/Auth/AuthController.php. Locate the validator function and replace the array of rules being passed to the Validator make method with a call to the newly created rules method in our User model.

REPLACE
return Validator::make($data, [
    'name' => 'required|max:255',
    'email' => 'required|email|max:255|unique:users',
    'password' => 'required|min:6|confirmed',
]);
WITH
return Validator::make($data, (new User)->rules());

Before going further, now would be a good time to test registering a new user, login, logout etc.. to make sure everyting works. All we have done so far aside from the code that artisan generated is move the User model and move the validation rules from the AuthController into it.

The next page covers using the api middleware in routes, creating the Validation\UserController for handling the XHR request and finally updating the templates and adding the JavaScript for Ajax.


Razor Foreach Loop with Index

I recently needed to get the index of an item in a razor foreach loop. This simple solution posted as a comment by Ian Mercer on Phil Haack’s blog did the trick easily.

Models/NameModel.cs
public class NameModel
{
	public int Id { get; set; }
	public string DisplayName { get; set; }
}

If we have a model called NameModel with a string DisplayName being returned to our view as a list object:

Views/Home/View.cshtml
@model List
@{
	if (Model != null)
	{
		//this gets you the item (item.value) and its index (item.i)
		foreach (var item in Model.Select((value,i) => new {i, value}))
		{
			<li>The index is @item.i and the value is @item.value.DisplayName</li>
		}
	}
}

jQuery Mobile Cascaded Selects using MVC4 and KnockoutJs

This post documents building cascading select inputs using jQuery and Knockout for the user interface and MVC4 to serve the content.

Step 1

OPEN or CREATE the MvcMobileApp that includes the Chinook Models and Data Access class [chinook-asp-net-mvc-4-web-application]

View

CREATE a view that will be used to render the cascading selects to the browser. In the Solution Explorer, right click on the Views folder and create a new folder names Chinook. Then right click on the new Chinook folder and Add a new View named CascadeSelect.

Controller

CREATE a controller class for the cascading selects. In the Solution Explorer, right click on the Controllers folder and add a new empty MVC controller named ChinookController

Below the public ActionResult Index() method, add these methods as shown below:

  1. CascadeSelect() returns the CascadeSelect View
  2. GetArtists() returns an json array of artists
  3. GetAlbums() returns an json array of albums by artist id
  4. GetTracks() returns an json array of tracks by album id

MvcMobileApp/Controllers/ChinookController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcMobileApp.Controllers
{
    public class ChinookController : Controller
    {
        //
        // GET: /Chinook/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult CascadeSelect()
        {
            return View();
        }

        public ActionResult GetArtists()
        {
            var artistList = Chinook.GetArtists();
            return this.Json(artistList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetAlbums(string id)
        {
            var albumList = Chinook.GetAlbums(Convert.ToInt32(id));
            return this.Json(albumList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetTracks(string id)
        {
            var trackList = Chinook.GetTracks(Convert.ToInt32(id));
            return this.Json(trackList, JsonRequestBehavior.AllowGet);
        }

    }
}

EDIT Views/Shared/_Layout.cshtml

  1. move the client script bundle loaders into the document head
  2. add a link to the knockoutjs script in the document head
  3. in the document body, look for the opening div data-role=”page”, and add id=”page” to it as follows:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>@ViewBag.Title</title>
        <meta name="viewport" content="width=device-width" />
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        @Styles.Render("~/Content/mobileCss", "~/Content/css")
        @Scripts.Render("~/bundles/modernizr")
        @Scripts.Render("~/bundles/jquery", "~/bundles/jquerymobile")
        @RenderSection("scripts", required: false)
        <script src="~/Scripts/knockout-2.1.0.js" type="text/javascript"></script>
    </head>
    <body>
        <div data-role="page" data-theme="b" id="page">
            <div data-role="header">
                @if (IsSectionDefined("Header")) {
                    @RenderSection("Header")
                } else {
                    <h1>@ViewBag.Title</h1>
                    @Html.Partial("_LoginPartial")
                }
            </div>
            <div data-role="content">
                @RenderBody()
            </div>
        </div>
    </body>
</html>

EDIT MvcMobileApp/Views/Chinook/CascadeSelect.cshtml, add this javascript to the bottom of the view.


<script type="text/javascript">
    //for jQueryMobile use this instead of document ready
    $("#page").live("pageinit", function (event) {

        getArtists();

    });

    var viewModel = function () {
        var self = this;
        self.artists = ko.observableArray([]);
        self.albums = ko.observableArray([]);
        self.tracks = ko.observableArray([]);

        //selected value observable and onchange subscription to fire data lookup for child select
        self.selectedAlbum = ko.observable();
        self.selectedAlbum.subscribe(function (value) {
            if (value) {
                getTracks(value);
            }
        });
        self.selectedArtist = ko.observable();
        self.selectedArtist.subscribe(function (value) {
            if (value) {
                getAlbums(value);
            }
        });
    };
    var viewModel = new viewModel();
    ko.applyBindings(viewModel);

    function getArtists() {
        $.getJSON("/Chinook/GetArtist", null, function (data) {
            viewModel.artists(data);
            // reset jQueryMobile selection label
            $("#select-artist").selectmenu("refresh");
        });
    }

    function getAlbums(artistId) {
        $.getJSON("/Chinook/GetAlbums/" + artistId, null, function (data) {
            viewModel.albums(data);
            // reset jQueryMobile selection label
            $("#select-album").selectmenu("refresh");
        });
    }

    function getTracks(albumId) {
        $.getJSON("/Chinook/GetTracks/" + albumId, null, function (data) {
            viewModel.tracks(data);
            // reset jQueryMobile selection label
            $("#select-track").selectmenu("refresh");
        });
    }
</script>
Insert this data bound markup above the javascript that was just added to CascadeSelect.cshtml
<label for="select-artist" class="ui-hidden-accessible">Artist</label>
<select name="select-artist" id="select-artist"
    data-bind="options: artists,
    optionsText: 'Name',
    optionsValue: 'ArtistId',
    optionsCaption: 'Select Artist',
    value: selectedArtist">
</select>

<div data-bind="visible: albums().length > 0">
    <label for="select-album" class="ui-hidden-accessible">Album</label>
    <select name="select-album" id="select-album"
        data-bind="options: albums,
        optionsText: 'Title',
        optionsValue: 'AlbumId',
        optionsCaption: 'Select Album',
        value: selectedAlbum">
    </select>
</div>

<div data-bind="visible: tracks().length > 0">
    <label for="select-track" class="ui-hidden-accessible">Track</label>
    <select name="select-track" id="select-track"
        data-bind="options: tracks,
        optionsText: 'Name',
        optionsValue: 'TrackId',
        optionsCaption: 'Select Track'">
    </select>
</div>

EDIT Views/Home/Index.cshtml and add this razor html helper code to the bottom that will render a link to the CascadeSelect view from the home view.

@Html.ActionLink("CascadeSelect", "CascadeSelect", "Chinook")

Run

Select F5 or one of the debug options to run the app.

Optimize

It is likely that on page load the first select does not populate before it can be accessed by the user. Therefore, we will use the server side view model data source for the select list when the view is returned to the page on load.

EDIT Controllers/ChinookController.cs

CHANGE
public ActionResult CascadeSelect()
{
    return View();
}
TO
public ActionResult CascadeSelect()
{
    //return model for first select
    var artistList = Chinook.GetArtists();
    return View(artistList);
}

EDIT Views/Chinook/CascadeSelect.cshtml

On the very first line, add
@model List

NEXT, REPLACE

<select name="select-artist" id="select-artist"
    data-bind="options: artists,
    optionsText: 'Name',
    optionsValue: 'ArtistId',
    optionsCaption: 'Select Artist',
    value: selectedArtist">
</select>
WITH
<select name="select-artist" id="select-artist"
    data-bind="value: selectedArtist">
        <option value="">Select Artist</option>
@{
    if (Model != null)
    {
        foreach (var item in Model)
        {
            <option value="@item.ArtistId">@item.Name</option>
        }
    }
}
</select>

NEXT, change the visible binding on the select-album parent div.
REPLACE

<div data-bind="visible: albums().length > 0">
WITH
<div data-bind="visible: selectedArtist">

NEXT, change the visible binding on the select-track parent div.
REPLACE

<div data-bind="visible: tracks().length > 0">
WITH
<div data-bind="visible: selectedArtist, visible: selectedAlbum">

NOTE: Bindings are applied in order from left to right. In the data-bind above, visible: selectedArtist is applied before visible: selectedAlbum.

In the javascript code block at the bottom of Views/Chinook/CascadeSelect.cshtml, DELETE these two lines of javascript:

getArtists();

self.artists = ko.observableArray([]);
AND

DELETE the function:

function getArtists() {
    $.getJSON("/Chinook/GetArtists", null, function (data) {
        viewModel.artists(data);
        // reset jQueryMobile selection label
        $("#select-artist").selectmenu("refresh");
    });
}

Review

After all of those changes, your Views/Chinook/CascadeSelect.cshtml view should like this:
@model List
@{
    ViewBag.Title = "CascadeSelect";
}

<h2>CascadeSelect</h2>

<label for="select-artist" class="ui-hidden-accessible">Artist</label>
<select name="select-artist" id="select1"
    data-bind="value: selectedArtist">
        <option value="">Select Artist</option>
@{
    if (Model != null)
    {
        foreach (var item in Model)
        {<a href="~/Scripts/">~/Scripts/</a>
            <option value="@item.ArtistId">@item.Name</option>
        }
    }
}
</select>

<div data-bind="visible: selectedArtist">
    <label for="select-album" class="ui-hidden-accessible">Album</label>
    <select name="select-album" id="select2"
        data-bind="options: albums,
        optionsText: 'Title',
        optionsValue: 'AlbumId',
        optionsCaption: 'Select Album',
        value: selectedAlbum">
    </select>
</div>

<div data-bind="visible: selectedArtist, visible: selectedAlbum">
    <label for="select-track" class="ui-hidden-accessible">Track</label>
    <select name="select-track" id="select3"
        data-bind="options: tracks,
        optionsText: 'Name',
        optionsValue: 'TrackId',
        optionsCaption: 'Select Track'">
    </select>
</div>

<script type="text/javascript">
    //for jQueryMobile use this instead of document ready
    $("#page").live("pageinit", function (event) {
        //getArtists();
    });

    var viewModel = function () {
        var self = this;
        self.albums = ko.observableArray([]);
        self.tracks = ko.observableArray([]);

        //selected value observable and onchange subscription to fire data lookup for child select
        self.selectedAlbum = ko.observable();
        self.selectedAlbum.subscribe(function (value) {
            if (value) {
                getTracks(value);
            }
        });
        self.selectedArtist = ko.observable();
        self.selectedArtist.subscribe(function (value) {
            if (value) {
                getAlbums(value);
            }
        });
    };
    var viewModel = new viewModel();
    ko.applyBindings(viewModel);

    function getAlbums(artistId) {
        $.getJSON("/Chinook/GetAlbums/" + artistId, null, function (data) {
            viewModel.albums(data);
            // reset jQueryMobile selection label
            $("#select-album").selectmenu("refresh");
        });
    }

    function getTracks(albumId) {
        $.getJSON("/Chinook/GetTracks/" + albumId, null, function (data) {
            viewModel.tracks(data);
            // reset jQueryMobile selection label
            $("#select-track").selectmenu("refresh");
        });
    }
</script>

Run

Select F5 or one of the debug options to run the app.

    References:
  • KnockoutJS Simplify dynamic JavaScript UIs by applying the Model-Vifew-View Model (MVVM) pattern.

Chinook ASP.NET MVC 4 Web Application

For this tutorial, you will need:

In Visual Studio, create a new C# ASP.NET MVC 4 Web Application:
Visual Studio 2012 New Project Dialog

Next, select the Mobile Application Project Template. This template includes jQuery, jQuery intellisense for Visual Studio, jQuery Mobile and Knockoutjs javascript files.
Visual Studio 2012 Project Template Dialog

If you haven’t done so yet, now would be a good time to install the Chinook database to your SQL Server.

Make sure you can Access your SQL Server [Configure Windows Firewall]

Open up the Web.config and replace

<connectionStrings>
 <add name="DefaultConnection" providerName="System.Data.SqlClient" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=aspnet-MvcMobileApp-20120922134208;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\aspnet-MvcMobileApp-20120922134208.mdf" />
</connectionStrings>

With *

<connectionStrings>
 <add name="ChinookConnection" providerName="System.Data.SqlClient" connectionString="Data Source=VGN-FW290\SQLEXPRESS;Initial Catalog=Chinook;Integrated Security=True" />
</connectionStrings>

* VGN-FW290 is the name of my computer. Replace this with your computer name

SQL Stored Procedures

Open up SQL Server Management Studio and connect to the server that is defined in your web.config connection string. In the Object Explorer(F8) Right click on the Stored Procedures node of the expanded Databases.Chinook tree and Select “New Stored Procedure”.

The first stored procedure you will create, GetArtists uses CTE (Common Table Expression) to handle the paging of the records. Especially in web applications, it is a best practice to utilize paging when returning records from large tables.

The next stored procedure you will create, GetAlbums is for selecting Album(s).

The third stored procedure you will create, GetTracks is for selecting Track(s).

SQL Function

New Scalar-value function

Create this user defined function to add a computed AlbumCount column to the Artist Table.

Now we need to bind the SQL Function to the Artist table. In the Object Explorer(F8), expand the Tables node and Artist table node. Right click on the Columns folder and select New Column from the context menu. Enter AlbumCount under Column Name and int under Data Type. Navigate to the Column Properties tab, expand the Computed Column Specification and enter “([dbo].[CountAlbums]([ArtistId]))” for the (Formula) value. Save the Artist table (Ctrl+S).

Data Models

Create a data model class for the Chinook data objects. In the Solution Explorer, right click on the Models folder and add a new file named ChinookModels.cs

MvcMobileApp/Models/ChinookModels.cs
using System;

namespace MvcMobileApp.Models
{
    public class AlbumModel
    {
        public int AlbumId { get; set; }
        public string Title { get; set; }
        public int ArtistId { get; set; }
    }

    public class ArtistModel
    {
        public int ArtistId { get; set; }
        public string Name { get; set; }
        public int AlbumCount { get; set; }
    }

    public class TrackModel
    {
        public int TrackId { get; set; }
        public string Name { get; set; }
        public int AlbumId { get; set; }
        public int MediaTypeId { get; set; }
        public int GenreId { get; set; }
        public string Composer { get; set; }
        public int Milliseconds { get; set; }
        public int Bytes { get; set; }
        public double UnitPrice { get; set; }
    }
}

Data Access

Create a data access class that can be used by any controller in our application to interface with the Chinook database. In the Solution Explorer, right click on the MvcMobileApp project and add a new class called Chinook. The result is a file named Chinook.cs in the root of our application.

MvcMobileApp/Chinook.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Web.Configuration;

namespace MvcMobileApp
{
    using Models;

    public class Chinook
    {
        public static List GetAlbums(int artistId)
        {
            List albumList = new List();
            SqlConnection conn = new SqlConnection(Config.ChinookConnection);
            try
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand("GetAlbums", conn);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@ArtistId", artistId);
                SqlDataReader dr = cmd.ExecuteReader();
                while (dr.Read())
                {
                    albumList.Add(new AlbumModel()
                    {
                        AlbumId = Convert.ToInt32(dr["AlbumId"]),
                        Title = dr["Title"].ToString(),
                        ArtistId = Convert.ToInt32(dr["ArtistId"]),
                    });
                }
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                conn.Close();
            }
            return albumList;
        }

        public static List GetArtists()
        {
            //get all artists
            List artistList = GetArtists(0,1);
            return artistList;
        }

        public static List GetArtists(int pageSize, int pageNumber)
        {
            List artistList = new List();
            SqlConnection conn = new SqlConnection(Config.ChinookConnection);
            try
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand("GetArtists", conn);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@PageSize", pageSize);
                cmd.Parameters.AddWithValue("@PageNumber", pageNumber);
                SqlDataReader dr = cmd.ExecuteReader();
                while (dr.Read())
                {
                    artistList.Add(new ArtistModel()
                    {
                        ArtistId = Convert.ToInt32(dr["ArtistId"]),
                        Name = dr["Name"].ToString(),
                        AlbumCount = Convert.ToInt32(dr["AlbumCount"])
                    });
                }
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                conn.Close();
            }
            return artistList;
        }

        public static List GetTracks(int albumId)
        {
            List trackList = new List();
            SqlConnection conn = new SqlConnection(Config.ChinookConnection);
            try
            {
                conn.Open();
                SqlCommand cmd = new SqlCommand("GetTracks", conn);
                cmd.CommandType = CommandType.StoredProcedure;
                cmd.Parameters.AddWithValue("@AlbumId", albumId);
                SqlDataReader dr = cmd.ExecuteReader();
                while (dr.Read())
                {
                    trackList.Add(new TrackModel()
                    {
                        TrackId = Convert.ToInt32(dr["TrackId"]),
                        Name = dr["Name"].ToString(),
                        AlbumId = Convert.ToInt32(dr["AlbumId"]),
                        MediaTypeId = Convert.ToInt32(dr["MediaTypeId"]),
                        GenreId = Convert.ToInt32(dr["GenreId"]),
                        Composer = dr["Composer"].ToString(),
                        Milliseconds = Convert.ToInt32(dr["Milliseconds"]),
                        Bytes = Convert.ToInt32(dr["Bytes"]),
                        UnitPrice = Convert.ToDouble(dr["UnitPrice"]),
                    });
                }
            }
            catch (Exception ex)
            {
                return null;
            }
            finally
            {
                conn.Close();
            }
            return trackList;
        }

        private class Config
        {
            static public String ChinookConnection { get { return WebConfigurationManager.ConnectionStrings["ChinookConnection"].ConnectionString; } }
        }

    }

}

Controller

CREATE a controller class for the cascading selects. In the Solution Explorer, right click on the Controllers folder and add a new empty MVC controller named ChinookController

Below the public ActionResult Index() method, add these methods as shown below:

  1. GetArtists() returns an json array of artists
  2. GetAlbums() returns an json array of albums by artist id
  3. GetTracks() returns an json array of tracks by album id

MvcMobileApp/Controllers/ChinookController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace MvcMobileApp.Controllers
{
    public class ChinookController : Controller
    {
        //
        // GET: /Chinook/

        public ActionResult Index()
        {
            return View();
        }

        public ActionResult GetArtists()
        {
            var artistList = Chinook.GetArtists();
            return this.Json(artistList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetAlbums(string id)
        {
            var albumList = Chinook.GetAlbums(Convert.ToInt32(id));
            return this.Json(albumList, JsonRequestBehavior.AllowGet);
        }

        public ActionResult GetTracks(string id)
        {
            var trackList = Chinook.GetTracks(Convert.ToInt32(id));
            return this.Json(trackList, JsonRequestBehavior.AllowGet);
        }

    }
}

C# Strip HTML and Attributes in MVC

If you are receiving this error message when posting content that contains HTML:

A potentially dangerous Request.Form value was detected from the client

Add ValidateInput(false) to your controller’s HttpPost method:

[HttpPost, ValidateInput(false)]

Take a look at this blog article for the class that does the HTML strip work, http://www.dijksterhuis.org/safely-cleaning-html-with-strip_tags-in-csharp/.

In my implementation, the list of allowed tags is a string in the web.config:

<configuration>
	<appSettings>
		<add key="HtmlAllowed" value="a,b,br,em,i,p,strong"/>
	</appSettings>
</configuration>

The settings class contains this line to set the HtmlAllowed web.config appSetting to a char array:

public static string[] HtmlAllowed { get { return ConfigurationManager.AppSettings["HtmlAllowed"].Split(",".ToCharArray()); } }

Here is an example of a call to the method to strip HTML posted from a form input named “MyTextarea” while passing in the allowed tags array parameter in the second argument:

StripHtml.StripTagsAndAttributes(collection["MyTextarea"].Trim(), Settings.HtmlAllowed );

Source code

using System;
using System.Text.RegularExpressions;

namespace StripHTML
{
    class MainClass
    {
        
        private static string ReplaceFirst(string haystack, string needle, string replacement)
        {
            int pos = haystack.IndexOf(needle);
            if (pos < 0) return haystack;
            return haystack.Substring(0,pos) + replacement + haystack.Substring(pos+needle.Length);
        }

        private static string ReplaceAll(string haystack, string needle, string replacement)
        {
             int pos;
             // Avoid a possible infinite loop
             if (needle == replacement) return haystack;
              while((pos = haystack.IndexOf(needle))>0)
                       haystack = haystack.Substring(0,pos) + replacement + haystack.Substring(pos+needle.Length);
                        return haystack;
        }       

        public static string StripTags(string Input, string[] AllowedTags)
        {
            Regex StripHTMLExp = new Regex(@"(<\/?[^>]+>)");
            string Output = Input;

            foreach(Match Tag in StripHTMLExp.Matches(Input))
            {
                string HTMLTag = Tag.Value.ToLower();
                bool IsAllowed = false;
                
                foreach(string AllowedTag in AllowedTags)
                {
                    int offset = -1;

                    // Determine if it is an allowed tag 
                    // "" , "');
                    if (offset!=0) offset = HTMLTag.IndexOf('<'+AllowedTag+' ');
                    if (offset!=0) offset = HTMLTag.IndexOf(" m.Groups[1].Value + "href..;,;.." + m.Groups[2].Value;
            MatchEvaluator ClassMatch = m => m.Groups[1].Value + "class..;,;.." + m.Groups[2].Value;
            MatchEvaluator UnsafeMatch = m => m.Groups[1].Value + m.Groups[4].Value;
            
            /* Allow the "href" attribute */
            Output = new Regex("()").Replace(Output,HrefMatch);

            /* Allow the "class" attribute */
            Output = new Regex("()").Replace(Output,ClassMatch);

            /* Remove unsafe attributes in any of the remaining tags */
            Output = new Regex(@"(<.*) .*=(\'|\""|\w)[\w|.|(|)]*(\'|\""|\w)(.*>)").Replace(Output,UnsafeMatch);

            /* Return the allowed tags to their proper form */
            Output = ReplaceAll(Output,"..;,;..", "=");
            
            return Output;
        }
    }
}

MVC 3 RSS Reader

Model

public class Feed
{
	public string Title { get; set; }
	public string Link { get; set; }
	public string Description { get; set; }
}

Controller

public ActionResult Index()
{

	XDocument feedXML = XDocument.Load("http://feeds.haacked.com/haacked");

	//select out a collection of anonymous types from RSS feed
	var feedCollection = from rss in feedXML.Descendants("item")
				select new Feed
				{
					Title = rss.Element("title").Value,
					Link = rss.Element("link").Value,
					Description = rss.Element("description").Value
				};

    return View(feedCollection);
}

View

@model IEnumerable<RssReader.Models.Feed> 
@{
	ViewBag.Title = "Index";
}

<h2>Index</h2>

<ul>
@foreach (var item in Model)
{
	<li>@item.Title</li>

}
</ul>

MVC 3 Razor on a Shared .NET Host

Required: ** Get using Web Platform Installer

Add Deployable Dependencies

The following steps are needed to deploy the MVC3 and Razor libraries to the shared server for the application to run. This is a new feature you get when you update your Visual Studio to SP1.

To get your MVC project running on the host, prepare it by right clicking on it in the solution explorer and select Add Deployable Dependencies.

Then you will get a dialog to select the dependencies you want to include when you deploy the web app. Select both ASP.NET MVC and ASP.NET Web pages with Razor syntax.

Knowledge Base > WinHost > IIS7 > Using the Microsoft IIS 7.0 Manager

MVC 3

A month ago, MVC 3 was released. Given it’s maturity level and features, I have decided it’s time to start building web apps with it instead of web forms.

Get MVC 3.

Looking for jquery-1.4.4-vsdoc.js? Create a new MVC3 Web App (jquery-1.4.4-vsdoc.js)

Other jQuery files packaged in the project template include:

version 1.7 jQuery UI
jquery-ui.js
jquery-ui.min.js

Bonus – intellisense for the jQuery validation plug-in 1.7 (jquery.validate-vsdoc.js)

Razor HTML Helpers

I will expand/update this as I learn more about it. For now just a few examples that I wanted to document.
Razor
@using (Html.BeginForm("HandleForm", "Home")){ }

@using (Html.BeginForm("HandleForm", "Home", FormMethod.Post, new { id = "myForm" })){ }

@using (Ajax.BeginForm("HandleForm", "Home", new AjaxOptions { UpdateTargetId = "myResults" }, new { id = "myForm" })) { }

@Html.ActionLink("Detail", "Detail", new { controller = "Artist", id = 1 })

@Html.ActionLink("Detail", "Detail", new { controller = "Artist", id = 1 }, new { @class = "myClass", title = "My Link Title" })

@Html.ActionLink("Detail", "Detail", new { controller = "Artist", id = 1 }, new { id = "myId", title = "My Link Title" })

* Note the razor escape “@” before class. This is because “class” is reserved.

Output
<form action="/Home/HandleForm" method="post"></form>

<form method="post" id="myForm" action="/Home/HandleForm"></form>

<form method="post" id="myForm" data-ajax-update="#myResults" data-ajax-mode="replace" data-ajax="true" action="/Home/HandleForm?Length=4"></form>

<a href="/Artist/Detail/1">Detail</a>

<a title="My Link Title" href="/Artist/Detail/1" class="myClass">Detail</a>

<a title="My Link Title" id="myId" href="/Artist/Detail/1">Detail</a>