Sunday, January 04, 2009
Two new additions were added to Unifico: filtering on fields and sorting on fields with the help of Html Extension methods.
A PagingSet class was defined to contain configuration options for the paging. In this way a View can still page on more than one list and maintain complete control over the Html rendered. Every string used in the Html Helpers is pulled from the configuration (A default is made available).
Several extension methods were added to the Html Helper to make rendering the controls easier.
The filtering html form can be rendered with <%= Html.FilterInput(Paging, "Go") %>
Where "go" is the button text.
A sortable column header can be rendered with <%= Html.PageSorting(Paging, "Email","E-Mail")%>
Where the paging configuration has been passed from the controller, leaving the paging control completely in the hands of the controller:
<%
PageResponse<User> Users = (PageResponse<User>)ViewData.Model;
PagingSet Paging = (PagingSet)ViewData["PagingSet"];
%>
View the complete "View" here, http://www.codeplex.com/unifico/SourceControl/changeset/view/1798#54414
View the complete Controller here, http://www.codeplex.com/unifico/SourceControl/changeset/view/1798#44699
The end result of all this is paging that executes through the PageRequest class, so that no changes are required of the Service and paging that executes in SQL. It also has the nice benefit of residing completely in the QueryString and not requiring changes to the Controller's Action's parameters.
An example path created from paging with sorting and filtering: /Admin/Account/Users?expr=asdf9&filterby=All&orderby=Name&asc=False&page=1
And the SQL it generates.
exec sp_executesql N'SELECT TOP (10) [t0].[UserID] AS [ID], [t0].[Name], [t0].[Password], [t0].[Email]
FROM [dbo].[User] AS [t0]
WHERE ([t0].[Name] = @p0) OR ([t0].[Name] = @p1)
ORDER BY [t0].[Name] DESC',N'@p0 nvarchar(5),@p1 nvarchar(5)',@p0=N'asdf9',@p1=N'asdf9'
Saturday, January 03, 2009
The Account Component in the Unifico Framework which is serving as a sample component now has an admin. Two supporting developments for the admin are also up: the paging HtmlHelper and overridable embedded views. Allowing for the embedded views to be overridden by placing new views in App.Web greatly eases development and should help in Component maintenance and versioning (new views can be made without rebuilding the component). I am also rather happy with the eas to which views are made overridable.
The paging turned out quite well, a few changes to the PageResponse<T> to implement IPageable made things fit really nicely. For example if PageResponse<User> is passed to the view from the controller, the paging can be generated with default settings in the snippet below. I also made use of my QueryString extension methods to make it work with populated QueryStrings (and have support for many pagers).
<%= Html.PageListing(Users) %>
http://www.codeplex.com/unifico
Friday, January 02, 2009
A while back I found an interesting post on embedding resources in MVC at "The Glass is Too Big" (http://www.wynia.org/wordpress/2008/12/05/aspnet-mvc-plugins/). Following the method was straightforward and allowed views to be embedded within a Component Class Library. Well, two extra items would be nice. It would be nice to not have to write that large plug-in path, and if a view was defined in the views folder it would be returned in lieu of the embedded view.
Adding this support required two modifications. First the AssemblyResourceProvider was modified to check for the view's existence by adding a Regex pattern match and checking for the file. If the file is found within the view folder, the file is returned, if not the embedded resource will return.
58 // Check to see if a file has been added to the views folder. If so, return that view.
59 Match m = Regex.Match(path, @"~/Plugin/[\w\.]+.dll/([\w\.]+).Views.([\w\.]+).aspx");
60 if (m.Success)
61 {
62 string assemblyMatch = m.Groups[1].Value;
63 string viewPath = m.Groups[2].Value;
64 string physicalPath = HttpContext.Current.Server.MapPath(String.Format("~/Views/{0}/{1}.aspx",
65 assemblyMatch, viewPath.Replace(".", "/")));
66 if (File.Exists(physicalPath))
67 return
File.Open(physicalPath,FileMode.Open);
68 }
http://www.codeplex.com/unifico/SourceControl/changeset/view/1491#9855
To avoid having to write out the plug-in strings I wrote an extension method to write it and return the ViewResult. Reflection is used to gather the assembly name.
9 public
static
class
ControllerPluginPathExtender
10 {
11 public
static
ViewResult PluginView(this
Controller controller)
12 {
13 string controllerName = controller.RouteData.GetRequiredString("controller");
14 string viewName = controller.RouteData.GetRequiredString("action");
15 string assemblyName = controller.GetType().Assembly.FullName.Split(',')[0];
16 string pluginPath = String.Format(
17 @"~/Plugin/{0}.dll/{0}.Views.{1}.{2}.aspx",
18 assemblyName,controllerName,viewName
19 );
20 return
new
ViewResult{
21 ViewName = pluginPath
22 };
23 }
24 }
http://www.codeplex.com/unifico/SourceControl/changeset/view/1491#52892
Now using the embedded option is less tedious, for example:
33
34 [Authorize(Roles="Admin")]
35 public
ActionResult Index()
36 {
37 ViewData["Title"] = "Account Admin Home";
38 ViewData["Message"] = "Welcome to the account admin";
39
40 return
this.PluginView();
41 //return View("~/Plugin/App.Account.dll/App.Account.Views.Admin.Index.aspx");
42 }
It would be nice to not have to use 'this', but I can't see a way around it without rebuilding MVC, not something I want to start doing. Also some caching and less reflection might be nice.
Update: The VewData and TempData have to be added to pass the models to the view
11 public static ViewResult PluginView(this Controller controller)
12 {
13 string controllerName = controller.RouteData.GetRequiredString("controller");
14 string viewName = controller.RouteData.GetRequiredString("action");
15 string assemblyName = controller.GetType().Assembly.FullName.Split(',')[0];
16 string pluginPath = String.Format(
17 @"~/Plugin/{0}.dll/{0}.Views.{1}.{2}.aspx",
18 assemblyName,controllerName,viewName
19 );
20 return new ViewResult{
21 ViewName = pluginPath,
22 ViewData = controller.ViewData,
23 TempData = controller.TempData
24 };
25 }
Wednesday, December 31, 2008
Unifico has a few paging helpers to make service methods easily paged from a client. Essentially the framework makes consuming a page request object as simple as calling .ToList() on an IQueryable source. The object, the PageRequest, has two collections, Filters and Sorts with several constructors to make instantiation easy. The collections are rather straight forward, providing filtering and sorting ability on a paging source. The uniqueness comes from the ability to request multiple filters and sorts together, along with a paging request. This request is then execute on the IQueryable source, so if DLINQ is used, results in exactly two database hits. A COUNT statement is executed to find out how many pages there are, and a single select statement providing the paged results. This is all done without exposing LINQ or IQueryable to the service's client.
Making an existing source page-able is rather simple, compare the two implementations.
Without paging implemented:
44 public
List<App.Account.Models.Role> GetRoles()
45 {
46 return accountRepository.GetRoles().ToList();
47 }
With paging implemented:
43 public
PageResponse<App.Account.Models.Role> GetRolePage(PageRequest PageRequest)
44 {
45 return accountRepository.GetRoles().ToPageResponse(PageRequest);
46 }
Utilizing the paging is almost painfully easy.
Grabbing the first page at a size of ten:
120 PageResponse<Role> rolePage = accountService.GetRolePage(new
PageRequest(0, 10));
Or grabbing the first page sorting by the role's name (ascending):
120 PageResponse<Role> rolePage = accountService.GetRolePage(new
PageRequest(0, 10, "Name", "string", true));
Or fully defining the page request:
120 accountService.GetRolePage(new
PageRequest
121 {
122 Index = 2,
123 PageSize = 10,
124 Filters = new
ContractList<Filter>(
125 new[]{new
Filter { BinaryExpression=FilterBinaryExpression.Equal, Parameter="Name", TypeName="String",Value="Admin"},
126 new
Filter { BinaryExpression=FilterBinaryExpression.GreaterThan, Parameter="Level", TypeName="Int32", Value=1}}),
127 Sorts = new
ContractList<Sort>(
128 new[] { new
Sort { Ascending = true, TypeName = "Int32", Parameter = "Level" } })
129 });
130 /* Executes
131 SELECT [t1].[RoleID] AS [ID], [t1].[Name], [t1].[Level] AS [Level]
132 FROM (
133 SELECT ROW_NUMBER() OVER (ORDER BY [t0].[Level]) AS [ROW_NUMBER], [t0].[RoleID], [t0].[Name], [t0].[Level]
134 FROM [dbo].[Role] AS [t0]
135 WHERE ([t0].[Level] > 1) AND ([t0].[Name] = 'Admin')
136 ) AS [t1]
137 WHERE [t1].[ROW_NUMBER] BETWEEN 20 + 1 AND 20 + 10
138 ORDER BY [t1].[ROW_NUMBER]
139 */
I'm sure you have notice the TypeName properties and wondered why they are there. They enable the helper's methods to work with the expression tree before the query has been executed. Notice that the paging occurs in SQL against the table names and columns, not against the models. While the syntax is nowhere near that of LINQ, it seals of the service and keeps the paging responsibility within the service without requiring the service to handle specific paging requests. A similar method can be used to search multiple sources, and will be added shortly. Ohh, and it works across WCF.
The Unifico Framework
One major aspect to Unifico is its organization through assemblies and namespacing. Assemblies are 'cut' by component to allow for component plug-ability. One major goal of the project is to allow for a component to be easily added to an existing site without conflicting with other equally significant components. With a few modifications (to come) Unifico allows for a whole component to be dropped in as a dll without recompilation by virtue of Structure Map. Currently, each component's assembly has to be added to structure map in a common class, the 'bootstrapper'.
Within each component there are several namespaces, which are critical to understanding a component.
- App.Component.Controllers – Where ASP.NET MVC Controllers for the component are placed. These are clients to the service.
- App.Component.DataAccess.Interfaces – The interfaces for the data repository(s). These are usually rather small, with a Save, Delete and Get for each model.
- App.Component.DataAccess.SqlServer –Where the SQL specific implementation of the interface resides.
- App.Component.Diagrams – Contains the class diagrams for the component's models.
- App.Component.HttpModules – An optional namespace where any HttpModules for the component would reside.
- App.Component.Models – A namespace containing the models for the component. Models are serialized when remoting is used.
- App.Component.Models.Forms – Contains the 'Form Models', essentially requests to perform an action. For example a 'UserForm' would be populated and sent to the UserService to request a new User.
- App.Component.Service – Defines the service for the component. It is here where the WCF and in-process service definitions are located. The 'real work' of a component is defined within the service implementation.
- App.Component.Views – Contains any embedded views for the component. Embedding views are optional, but are used in the examples provided.
Without delving into details, that introduces the parts of a component's organization. Another aspect to a component is its physical organization. That is, where are various parts hosted in a distributed situation? The physical boundaries are defined by the interfaces, particularly the service interfaces. Within the service the application is hosted on the WCF service host. Outside of the service the application is hosted within the website.
Though very brief, I hope this helps anyone taking a look at the code. As always, any thoughts are appreciated.
First let me say that the code is in early state and publishing it is intended to gauge interest and recruit developers. What is the Unifico Framework (UF)? It's an architecture that has evolved while addressing various concerns in several real world projects. Currently it's nothing more than an example, and at that an example implementation of a simple example. In this example however many issues are addressed such as scalability, dependency injection, test driven development, and plug-in ability.
Written in C# against MVC Beta and ASP.NET 3.5.1 the code currently recreates the account 'component' of the MVC example template with a simple SQL Repository. The ASP.NET Membership Provider is completely replaced with a component written against the Unifico Framework's pattern. In this example several things are achieved:
- A Service Oriented Architecture (SOA) ready to be remoted through Windows Communication Foundation while using StructureMap to provide dependency injection from WCF.
- A repository based data source also protected with dependency injection that is exposed through in a pipes and filters manor. LINQ to SQL is used within a particular repository implementation, but is in no way exposed to the service.
- A Model View Controller (MVC) pattern is also used. The only objects returned from a service are models, and all controller activity is within the service itself. Virtual Paths are used to store the views within the component's assembly.
- Utility methods have been developed to provide paging across the service interfaces while leaving the 'control' in the hands of the service. The paging methods support multiple filters and multiple sorts within any paging method for a general service.
- The framework supports unit testing, however unit tests need to be added specifically for the account service. A project has been setup to show where this is done.
- 'Form' Models are used for requests to a service to allow all validation for a request to occur within the service. The responsibility of form validation is retained within a service in this manor, and prevents outside code from creating models.
There is a lot within the example, and this post will be followed with posts about specific areas. My hope in this early release is to incite interest from other developers in the community for two reasons. First, it would be nice to have the framework reviewed. Second, I hope a few developers would be interested in joining the project. It's released under the MIT license and provides a nice foundation to develop components against.
Check the project out at : Unifico Framework
Saturday, November 15, 2008
A simple way to perform a 'wget' on a url with c#. This method will return the 'html source' of a webpage.
static
string GetContent(string url)
{
WebClient client = new
WebClient();
client.Headers.Add("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)");
Stream data = client.OpenRead(url);
StreamReader reader = new
StreamReader(data);
string s = reader.ReadToEnd();
data.Close();
reader.Close();
return s;
}
Saturday, November 01, 2008
Note that if you get a BSOD during a commit/update its quite possible a fresh checkout is in order. This was definitely the case for me… Commit often :)
(and replace RAM)
Friday, October 17, 2008
I upgraded SQL on my laptop and during the process managed to 'lose' my database…. Total n00b move. I couldn't recover the database file (it was over a gig and was quickly written over). Determined not to recreate the work since the last backup, I kept searching for a solution. Then it hit me; pull the database from the data context (dbml) I created with SQLMETAL. Simply call CreateDatabase(), and 'bam' database recovered. Whew!!!
Monday, October 13, 2008
If you use LINQ, check this out, it's definitely worth the time. You can execute LINQ statements at the break point.
http://weblogs.asp.net/scottgu/archive/2007/07/31/linq-to-sql-debug-visualizer.aspx
"One of the nice development features that LINQ to SQL supports is the ability to use a "debug visualizer" to hover over a LINQ expression while in the VS 2008 debugger and inspect the raw SQL that the ORM will ultimately execute at runtime when evaluating the LINQ query expression."