Implement an AJAX form in the MVC Framework

The MVC Framework (now in version 1.0) is Microsoft’s implementation of the MVC software pattern in ASP.Net.

Here, my model is using LINQ to SQL against my empty database called GLOSSARY with one table called TERMS with three columns (Id, Term, Definition). Hopefully, I have kept the scenario simple enough so you can follow me.

In this sample, I have one controller, one view, and one partial view. A partial view is basically an ASP.Net user control (even the same file extension). The partial is a list of terms. The view contains the partial and two identical forms allowing users to filter the list (one POST and one AJAX).

I hope this makes sense to you, because AJAX in MVC is pretty darn easy:

//
// GlossaryController.cs

public ActionResult Index()
{
return View(Models.Term.SelectAll());
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(FormCollection collection)
{
// used by non-AJAX form
string _SearchTerm;
_SearchTerm = collection["SearchTerm"];
// don't indicate the partial here, the view will call it
return View(Models.Term.Where(x => x.Term.Contians(_SearchTerm)));
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index2(FormCollection collection)
{
// used by AJAX form
string _SearchTerm;
_SearchTerm = collection["SearchTerm"];
// you must indicate the partial here, it's all you want to render
return View("Search", Models.Term.Where(x => x.Term.Contians(_SearchTerm)));
}


And now for the view(s):


// The view: Index.aspx

<!-- the non-AJAX form to filter the list -->
<% using (Html.BeginForm()) { %>
<fieldset>
<legend>Search</legend>
<input name="SearchTerm" />
<input type="submit" value="Go" />
</fieldset>
<% } %>

<!-- the AJAX form to filter the list -->
<script src="/Scripts/MicrosoftAjax.js" type="text/javascript"></script>
<script src="/Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<% using (Ajax.BeginForm("Index2", new AjaxOptions { UpdateTargetId = "results" })) { %>
<fieldset>
<legend>Search</legend>
<input name="SearchTerm" />
<input type="submit" value="Go" />
</fieldset>
<% } %>

<!-- render the partial inside the view -->
<div id="results">
<% Html.RenderPartial("Search", Model); %>
</div>

// The partial: Search.ascx (a default LIST T4 template, nothing special/custom)

SQL Server 2008; fun tSQL syntax to shrink your queries

Here are most of the more basic syntax updates in SQL 2008. Enjoy.

-- old-school variables ;)

declare @x int
declare @y int
declare @z int

set @x = 1
set @y = 2
set @z = 3

-- combined initialization

declare
@a int
,@b int
,@c int

-- compound assigned

declare
@d int = 1
,@e int = 2
,@f int = 3

-- compound operator

set @i += 8
set @i -= 8
set @i /= 8
set @i *= 8
set @i %= 3

-- bitwise operators

set @i &= 1
set @i |= 1
set @i ^= 1

SQL Server 2008 new “grouping” tSQL candy

So many new features in SQL 2008. I presented on the updates at Trifecta a few months ago and never got around to posting some of my samples on my blog. Sorry about that if you were looking. Here’s the bit about grouping syntax. The code should create the sample table and data for you and run right away.

insert into Sales values
(1999, 'Q1', 'Jerry', 123)
,(1999, 'Q2', 'Jerry', 234)
,(1999, 'Q3', 'Jerry', 345)
,(1999, 'Q4', 'Jerry', 456)

select * from
(
values (1, 2), (2, 3), (3, 4)
) as DerivedTable(Col1, Col2)

-- ROLLUP = Generates the simple GROUP BY aggregate rows, plus subtotal or super-aggregate rows, and also a grand total row.

select
Year
,AVG(Amount) as AverageSales
from
Sales
group by
Year WITH ROLLUP

-- GROUPING = Indicates whether a specified column expression in a GROUP BY list is aggregated or not.

select
Year
,AVG(Amount) as Average
,GROUPING(Year) as [YearRollUp?]
from
Sales
group by
Year WITH ROLLUP

-- GROUPING SET = Specifies multiple groupings of data in one query.

select
Year
,Quarter
,AVG(Amount) as Average
,GROUPING(Year) as [YearRollUp?]
from
Sales
group by
GROUPING SETS ((Year, Quarter))

select
Year
,Quarter
,AVG(Amount) as Average
,GROUPING(Year) as [YearRollUp?]
from
Sales
group by
GROUPING SETS ((Year, Quarter), (Year), ())

-- GROUPING_ID = Is a function that computes the level of grouping.

select
Year
,Quarter
,AVG(Amount) as Average
,GROUPING_ID(Year, Quarter) as [x]
from
Sales
group by
GROUPING SETS ((Year, Quarter), (Year), ())

-- use it in the having

declare @level int = 1

select
Year
,Quarter
,AVG(Amount) as Average
,GROUPING_ID(Year, Quarter) as [x]
from
Sales
group by
GROUPING SETS ((Year, Quarter), (Year), ())
having
GROUPING_ID(Year, Quarter) = @level


-- CUBE = Generates simple GROUP BY aggregate rows, the ROLLUP super-aggregate rows, and cross-tabulation rows.

select
Year
,Quarter
,AVG(Amount) as Average
from
Sales
group by
cube(Year, Quarter, SalesPerson)
order by
Year, Quarter, Average

Passing a table-type parameter in ADO.Net

In SQL, you have always been able to create a custom type. But now you have the ability to create a custom type that is a TABLE type. With a TABLE type you literal define columns and column types. But you can then use this type as the input parameter type of a stored procedure. An age-old problem finally solved. How do you do it? I have a simple working demo below that shows the SQL and the ADO.Net code in C#. Happy coding!

// tSQL setup scripts
// create the SQL table

create table Bugs
(
BugId int primary key
identity(1,1)
,Title varchar(50)
,Details varchar(50)
)

// create the SQL table TYPE

create type BugsType as table
(
Title varchar(50)
,Details varchar(50)
)

// create the SQL insert stored proc

create proc BugsInsert
(@x BugsType readonly) as
set nocount on
insert into Bugs
select * from @x

declare @p BugsType
insert into @p values
('Login Fails', 'Password rejected')
,('Login Fails', 'Username rejected')
exec BugsInsert @p

// C# // how to insert a table-type parameter using ADO.Net

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;

namespace TableValuedParameters
{
public partial class Form1 : Form
{
DataTable m_DataTable = new DataTable();
public Form1()
{
InitializeComponent();
m_DataTable.Columns.Add(new DataColumn("Title", typeof(string)));
m_DataTable.Columns.Add(new DataColumn("Details", typeof(string)));
// you will need to add your own dataGridView to your form
dataGridView1.DataSource = m_DataTable;
}

// this is the save button event handler (this form only inserts)
private void button1_Click(object sender, EventArgs e)
{
string _ConnStr = @"server=.\sql2008express;database=jerry;integrated security=sspi;";
using (SqlConnection _SqlConnection = new SqlConnection(_ConnStr))
{
_SqlConnection.Open();
SqlCommand _SqlCommand = new SqlCommand("BugsInsert", _SqlConnection);
_SqlCommand.CommandType = CommandType.StoredProcedure;
// here's the magic, pass a datatable
_SqlCommand.Parameters.AddWithValue("x", m_DataTable);
_SqlCommand.ExecuteNonQuery();
}
this.Close();
}
}
}

LINQ to SQL Entity Base Class

So many things in LINQ to SQL are cool. But the handling of entities in a asp.net application drove me crazy. It was either the entity is already attached, different then the one in the database, or created with a different data context. So, I created my own base class that handles this and some other common issues with LINQ.
 
Let’s discuss.
 
The entities created by LINQ to SQL do not have a base class. As a result, it is VERY easy for them to inherit from anything, including my custom base class. Because the entity classes are already partial classes, all you need to do is create another class with the same name and the partial keyword. In addition, reference System.Runtime.Serialization. Done.
 
This is truly my gift to you. It took me a good 3 days to finally get all this working right. And the dynamic lambda stuff there (see the SelectOne() method) – that alone might make all this worth it. Nonetheless, it’s all yours. You’re welcome. :)
 
Here’s the code snippet to get you there (entity name = “Term”):
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Runtime.Serialization;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq.Expressions;
using System.Reflection;

namespace MvcApplication1
{
public partial class Term : CustomEntityBase<Term, GlossaryDataContext, int>
{
static public IQueryable<Term> Search(string value)
{
return (Program.NewDataContext as GlossaryDataContext).Terms.Where(x => x.Word.Contains(value));
}
}

#region Implementation

// TODO: move to own file

public class Program
{
static private object m_DataContext;
static public object DataContext
{
get
{
if (m_DataContext == null)
m_DataContext = NewDataContext;
return m_DataContext;
}
}
static public object NewDataContext
{
get
{
return new GlossaryDataContext(); // custom connection string
}
}
}

// TODO: move to own file

public interface ICustomEntitySimple
{
void Update();
void Delete();
void Insert();
}

// TODO: move to own file

public interface ICustomEntity<E, C, T> : ICustomEntitySimple
{
string __KeyName
{
get;
}
T __KeyValue
{
get;
}
E Select(T key);
}

// begin base class

// TODO: reference System.Runtime.Serialization

[DataContract]
public abstract class CustomEntityBase<E, C, T> : ICustomEntity<E, C, T>
where E : class
where C : System.Data.Linq.DataContext
{
public CustomEntityBase()
{
}

public string __KeyName
{
get
{
if ((Program.DataContext as C).Mapping.GetTable(typeof(E)).RowType.IdentityMembers.Count == 0)
throw new Exception("No Primary Key set for entity");
if ((Program.DataContext as C).Mapping.GetTable(typeof(E)).RowType.IdentityMembers.Count > 1)
throw new Exception("Compound Primary Key detected; not supported");

string _KeyName;
_KeyName = (Program.DataContext as C).Mapping.GetTable(typeof(E)).RowType.IdentityMembers[0].Name;
if (string.IsNullOrEmpty(_KeyName))
throw new Exception("Primary Key property not found");

return _KeyName;
}
}
public T __KeyValue
{
get
{
PropertyInfo _KeyProperty;
_KeyProperty = typeof(E).GetProperty(__KeyName);
if (_KeyProperty == null)
throw new Exception("Primary Key property not found");

return (T)_KeyProperty.GetValue(this, null);
}
}

public static Table<E> SelectAll()
{
C _Context;
_Context = Program.NewDataContext as C;

Table<E> _Table;
_Table = _Context.GetTable(typeof(E)) as Table<E>;

return _Table;
}

public static E SelectOne(T key)
{
string _KeyName;
_KeyName = (Program.DataContext as C).Mapping.GetTable(typeof(E)).RowType.IdentityMembers[0].Name;

if (string.IsNullOrEmpty(_KeyName))
throw new Exception("Primary Key property not found");

// build query

ParameterExpression _ParameterExpression;
_ParameterExpression = Expression.Parameter(typeof(E), "x");

MemberExpression _LeftExpression;
_LeftExpression = MemberExpression.Property(_ParameterExpression, _KeyName);

Expression _RightExpression;
_RightExpression = Expression.Constant(key);

BinaryExpression _BinaryExpression;
_BinaryExpression = MemberExpression.Equal(_LeftExpression, _RightExpression);

Expression<Func<E, bool>> _LambdaExpression;
_LambdaExpression = Expression.Lambda<Func<E, bool>>
(
_BinaryExpression,
new ParameterExpression[] { _ParameterExpression }
);

C _Context;
_Context = Program.DataContext as C;

Table<E> _Table;
_Table = _Context.GetTable(typeof(E)) as Table<E>;

return _Table.SingleOrDefault(_LambdaExpression);
}

public virtual E Select(T key)
{
return SelectOne(key);
}

#region Helpers

private E DetatchedEntity
{
get
{
StringWriter _StringWriter;
_StringWriter = new StringWriter();
using (XmlWriter _XmlWriter = XmlWriter.Create(_StringWriter))
{
DataContractSerializer _DataContractSerializer;
_DataContractSerializer = new DataContractSerializer(this.GetType());
_DataContractSerializer.WriteObject(_XmlWriter, this);
}

string _SerializedValue;
_SerializedValue = _StringWriter.ToString();

using (StringReader _StringReader = new StringReader(_SerializedValue))
{
using (XmlReader _XmlReader = XmlReader.Create(_StringReader))
{
DataContractSerializer _DataContractSerializer;
_DataContractSerializer = new DataContractSerializer(this.GetType());

object _RawObject;
_RawObject = _DataContractSerializer.ReadObject(_XmlReader);

return (E)_RawObject;
}
}
}
}

private void Sync(E detachedEntity)
{
foreach (var _Property in this.GetType().GetProperties())
{
object _DetatchedValue;
_DetatchedValue = _Property.GetValue(detachedEntity, null);
if (_Property.CanWrite)
_Property.SetValue(this, _DetatchedValue, null);
}
}

private void ProcessInsert(ref E entity)
{
PropertyInfo _DateCreated;
_DateCreated = entity.GetType().GetProperty("DateCreated");
if (_DateCreated != null)
_DateCreated.SetValue(entity, DateTime.Now, null);
ProcessUpdate(ref entity);
}

private void ProcessUpdate(ref E entity)
{
PropertyInfo _DateModified;
_DateModified = entity.GetType().GetProperty("DateModified");
if (_DateModified != null)
_DateModified.SetValue(entity, DateTime.Now, null);
}

#endregion

#region ICustomEntity

public void Update()
{
C _Context;
_Context = Program.NewDataContext as C;

Table<E> _Table;
_Table = _Context.GetTable(typeof(E)) as Table<E>;

E _Entity = DetatchedEntity;
ProcessUpdate(ref _Entity);
_Table.Attach(_Entity);
_Context.Refresh(RefreshMode.KeepCurrentValues, _Entity);

_Context.SubmitChanges();
Sync(_Entity);
}

public void Delete()
{
C _Context;
_Context = Program.NewDataContext as C;

Table<E> _Table;
_Table = _Context.GetTable(typeof(E)) as Table<E>;

E _Entity = DetatchedEntity;
_Table.Attach(_Entity);
_Table.DeleteOnSubmit(_Entity);

_Context.SubmitChanges();
Sync(_Entity);
}

public void Insert()
{
C _Context;
_Context = Program.NewDataContext as C;

Table<E> _Table;
_Table = _Context.GetTable(typeof(E)) as Table<E>;

E _Entity = DetatchedEntity;
ProcessInsert(ref _Entity);
_Table.InsertOnSubmit(_Entity);

_Context.SubmitChanges();
Sync(_Entity);
}

#endregion
}

#endregion
}