Data Abstract and ASP.NET MVC
Introduction
Recently I’ve been asked if it is possible to use two exciting technologies – Data Abstract for .NET and ASP.NET MVC – together.
So I decided to write this little blogpost to show you how an ASP.NET MVC website is working with a Data Abstract-powered data provider. The created website will be really simple, without any fancy jQuery UI effects (they would be far beyond the scope of this article).
The created ASP.NET website will show a list of customers and the sample PC Trade database and it will retrieve data via a 2-tier Data Abstract-powered class library hidden behind an interface.
Data Source
Data Abstact Class Library
The first step is to create a data provider assembly. Create a new RemObjects Data Abstact Class Library project and name it, say, MvcSample.Backend.
Create a new 2-tier application that uses a PCTrade-SQLite datasource. Be sure to uncheck the ‘Add Server Channel to the project‘, ‘Add Login Service‘ and ‘Add support for Business Rules Scripting‘ options.
We will use DA LINQ Table Definitions and Model classes.
Rename the TableDefinitions_MvcSample.BackendDataset.cs file to MvcSample.Model.cs. Open it and correct the namespace from MvcSample.Backend.MvcSample.BackendDataset to MvcSample.Model, as well. Note that all classes defined in this file are partial, so you can easily add new properties and methods to them when needed.
Also rename the Customers class to Customer.
Note that these changes will be lost once you regenerate the TableDefinitions file, so in most cases, it is much better to rename the corresponding Schema Table instead.
At this point we can already use the MvcSample.Backend assembly as data source for the ASP.NET MVC application.
But let’s improve it by hiding all Data Abstract-specific details behind interfaces and static methods.
Data Access Engine startup
Let’s add a static Repository class with a single (for now) method:
usingMvcSample.Backend; namespace MvcSample.DataLayer{publicstaticclass Repository {privatestatic Engine engine; publicstaticvoid Initialize(){if(Repository.engine==null) Repository.engine=new Engine();}}} |
Data Access Interface
Now we need to add a data access interface and implement it. This approach may look over-complicated for this sample, but it will later allow to create a more testable application (you will, for example, be able to mock the data access interface in unit tests and test appliction logic agains data loaded from memory instead of the database). Another advantage of hiding all data access behind an interface is that the actual ASP.NET MVC application will be fully separated from the data access layer (as it should be).
The interface is:
usingSystem;usingSystem.Collections.Generic;usingMvcSample.Model; namespace MvcSample.DataLayer{publicinterface IDataLayer { IList<Customer> GetAllCustomers(); void CreateCustomer(Customer customer);void UpdateCustomer(Customer customer); Customer GetCustomer(String id);void DeleteCustomer(String id);}} |
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingRemObjects.DataAbstract.Linq;usingMvcSample.Backend;usingMvcSample.Model; namespace MvcSample.DataLayer{class DataLayer : IDataLayer {private DataModule fDataModule;private LinqDataAdapter fDataAdapter; public DataLayer(DataModule dataModule){this.fDataModule= dataModule;this.fDataAdapter=this.fDataModule.DataAdapter;} public IList<Customer> GetAllCustomers(){var query =from c inthis.fDataAdapter.GetTable<Customer>()select c; return query.ToList<Customer>();} publicvoid CreateCustomer(Customer customer){this.fDataAdapter.GetTable<Customer>().InsertRow(customer);this.fDataAdapter.ApplyChanges();} public Customer GetCustomer(String id){return(from c inthis.fDataAdapter.GetTable<Customer>()where c.Id== id select c).FirstOrDefault();} publicvoid UpdateCustomer(Customer customer){this.fDataAdapter.GetTable<Customer>().UpdateRow(customer);this.fDataAdapter.ApplyChanges();} publicvoid DeleteCustomer(String id){this.fDataAdapter.GetTable<Customer>().DeleteRow(new Customer(){ Id = id });this.fDataAdapter.ApplyChanges();}}} |
publicstatic IDataLayer GetDataLayer(){ DataModule module =new DataModule();// In more advanced scenarios, one could use an ObjectPool// to provide a pool of data modules instead of// instantiating a new data module for each request. returnnew DataLayer(module);} |
ASP.NET MVC Web Site
Add a new ASP.NET MVC 3 project to the solution and name it MvcSample.Web. In the project properties select an ‘Empty’ project template and the Razor view engine and enable HTML5 markup.
Set the MvcSample.Web project as StartUp one and add the MvcSample.Backend project to its references.
Build the solution (so the ASP.NET MVC helpers will know about our model classes). I won’t use all the advanced ASP.NET MVC helpers stuff like staffolding etc. and will create the application by myself, step-by-step.
Now we need to add a controller. We need an empty controller named CustomersController.
Right-click on the ‘Controllers’ folder of your ASP.NET MVC project and select the ‘Add…’ -> ‘Controller’ menu item.
The controller class implementation is really simple (for now):
usingSystem;usingSystem.Collections.Generic;usingSystem.Web.Mvc;usingMvcSample.DataLayer;usingMvcSample.Model; namespace MvcSample.Web.Controllers{publicclass CustomersController : Controller {//// GET: /Customers/// GET: /Customers/Index/public ActionResult Index(){ IList<Customer> data = Repository.GetDataLayer().GetAllCustomers();returnthis.View(data);}}} |
The next step is to add a view that will list Customers. Unfortunately, Visual Studio refuses to create a strongly-typed view for me automatically. So I created an empty view and added all the needed Razor code:
@model IEnumerable<MvcSample.Model.Customer>
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<p>@ViewBag.Message</p>
<p>@Html.ActionLink("Create New", "Create")</p>
<table>
<tr>
<th>
Name
</th>
<th>
Phone
</th>
<th>
Address
</th>
<th>
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Phone)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
@Html.ActionLink("Details", "Details", new { id = item.Id }) |
@Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
It is really annoyng that ASP.NET MVC failed to create a dummy view for me. But considering that these views are usually thrown away anyway (because all the jQuery stuff requires way more advanced View code to be created), this is not really a huge problem.
Unfortunately, if you start the MVC application now, it will fail with an exception because we forgot to register the Customer route. Open the Global.asax.cs file and and correct the RegisterRoute method:
publicstaticvoid RegisterRoutes(RouteCollection routes) routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute("Default", // Route name"{controller}/{action}/{id}", // URL with parametersnew{ controller ="Customers", action ="Index", id = UrlParameter.Optional}// Parameter defaults);} |
protectedvoid Application_Start(){ AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); MvcSample.DataLayer.Repository.Initialize();} |
Data Abstract is not able to load the .daConnectionFile because the Assembly.GetEntryAssembly() method used to determine the name of the .daConnections resource returns
null for ASP.NET applications. We have to change the way connections are loaded in the Engine constructor:
public Engine(){this.InitializeComponent(); if(!RemObjects.DataAbstract.Server.Configuration.Loaded) RemObjects.DataAbstract.Server.Configuration.Load(); XmlDocument lConnectionsXml =new XmlDocument(); lConnectionsXml.Load(typeof(Engine).Assembly.GetManifestResourceStream(typeof(Engine).Assembly.GetName().Name+".MvcSample.Backend.daConnections"));this.connectionManager.LoadFromXml(lConnectionsXml);} |
And, as if this was not enough, look at this screenshot:
It took no more than 5 minutes to add all the code needed to provide a simple editor and a details view for customer. The controller class now looks like this:
usingSystem;usingSystem.Collections.Generic;usingSystem.Web.Mvc;usingMvcSample.DataLayer;usingMvcSample.Model; namespace MvcSample.Web.Controllers{publicclass CustomersController : Controller {//// GET: /Customers/// GET: /Customers/Index/public ActionResult Index(){ IList<Customer> data = Repository.GetDataLayer().GetAllCustomers();returnthis.View(data);} public ActionResult Details(String id){ Customer customer = Repository.GetDataLayer().GetCustomer(id); returnthis.View(customer);} public ActionResult Edit(String id){ Customer customer = Repository.GetDataLayer().GetCustomer(id); returnthis.View(customer);} [HttpPost]public ActionResult Edit(Customer customer){ Repository.GetDataLayer().UpdateCustomer(customer); returnthis.RedirectToAction("Index");}}} |
@model MvcSample.Model.Customer
@{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<fieldset>
<legend>Customer</legend>
<div class="display-label">Name</div>
<div class="display-field">
@Html.DisplayFor(model => model.Name)
</div>
<div class="display-label">Phone</div>
<div class="display-field">
@Html.DisplayFor(model => model.Phone)
</div>
<div class="display-label">Address</div>
<div class="display-field">
@Html.DisplayFor(model => model.Address)
</div>
</fieldset>
<p>
@Html.ActionLink("Edit", "Edit", new { id=Model.Id }) |
@Html.ActionLink("Back to List", "Index")
</p>
and
@model MvcSample.Model.Customer
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>BookDetails</legend>
@Html.HiddenFor(model => model.Id)
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Phone)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Phone)
@Html.ValidationMessageFor(model => model.Phone)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Address)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Address)
@Html.ValidationMessageFor(model => model.Address)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
Conclusion
We have created a simple ASP.NET MVC application that gets its data via Data Abstract. Only minimal changes were needed in the Data Abstract access class library, caused by the ASP.NET specifics.
The ASP.NET MVC code is not even aware of its data source, because all Data Abstract specifics are hidden behind a simple interface.