patterncsharpMinor
ASP controller for tracking equipment
Viewed 0 times
trackingcontrollerequipmentforasp
Problem
This was originally posted here. I'm hoping Code Review will be a little more helpful in giving me a concrete direction.
I've been working on a project based off of this tutorial.
Unfortunately, the controller classes in this are quite dense and have terrible separation of concerns. I should have realized this originally but, I ran with it and now am stuck dealing with the terrible design from this tutorial with my added features/bad decisions. I've been stuck and overthinking this.
In my case, I have 3 controllers: Hardware, Employee, Project. They access the database, filter the data and pass them along to the view. I also create certain UI components in the controller (see SelectList in the Create function below).
I want to break down these controllers into smaller, reusable parts. Filtering for example, could be re-used by different models but I can't quite abstract this in my head. There's a lot going on in most controller. Here's one of them for example:
```
public class HardwareController : Controller
{
private LATTContext db = new LATTContext();
// GET: Hardware
public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
ViewBag.CurrentSort = sortOrder;
ViewBag.SerialNoParm = sortOrder == "serialNo" ? "serialNo_desc" : "serialNo";
ViewBag.CabModelParm = sortOrder == "cabModel" ? "cabModel_desc" : "cabModel";
ViewBag.PlatformParm = sortOrder == "plat" ? "plat_desc" : "plat";
ViewBag.VendorParm = sortOrder == "vendor" ? "vendor_desc" : "vendor";
ViewBag.CategoryParm = sortOrder == "category" ? "category_desc" : "category";
ViewBag.EmployeeParm = sortOrder == "employee" ? "employee_desc" : "employee";
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewBag.CurrentFilter = searchString;
var hardwares = from h in db.hardwares
I've been working on a project based off of this tutorial.
Unfortunately, the controller classes in this are quite dense and have terrible separation of concerns. I should have realized this originally but, I ran with it and now am stuck dealing with the terrible design from this tutorial with my added features/bad decisions. I've been stuck and overthinking this.
In my case, I have 3 controllers: Hardware, Employee, Project. They access the database, filter the data and pass them along to the view. I also create certain UI components in the controller (see SelectList in the Create function below).
I want to break down these controllers into smaller, reusable parts. Filtering for example, could be re-used by different models but I can't quite abstract this in my head. There's a lot going on in most controller. Here's one of them for example:
```
public class HardwareController : Controller
{
private LATTContext db = new LATTContext();
// GET: Hardware
public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
ViewBag.CurrentSort = sortOrder;
ViewBag.SerialNoParm = sortOrder == "serialNo" ? "serialNo_desc" : "serialNo";
ViewBag.CabModelParm = sortOrder == "cabModel" ? "cabModel_desc" : "cabModel";
ViewBag.PlatformParm = sortOrder == "plat" ? "plat_desc" : "plat";
ViewBag.VendorParm = sortOrder == "vendor" ? "vendor_desc" : "vendor";
ViewBag.CategoryParm = sortOrder == "category" ? "category_desc" : "category";
ViewBag.EmployeeParm = sortOrder == "employee" ? "employee_desc" : "employee";
if (searchString != null)
{
page = 1;
}
else
{
searchString = currentFilter;
}
ViewBag.CurrentFilter = searchString;
var hardwares = from h in db.hardwares
Solution
Index
It would appear you primarily do three things here. Sort, Filter, and Page. There's not much to say about your paging, but the sorting and filtering logic clutter the action. Try creating an object that does the sorting and filtering for you. Something like this:
Note: I didn't include all the logic you need, however; I did include what I believe to be enough logic for you to get the idea.
With this idea there are a few things you can do:
Other Actions
The only other thing I see is that the following code is repeated in many of your actions.
I commonly create a method to call this. The only slight caveat to this is if you ever need to change one of the actions calling it (not that common, but it happens sometimes).
View
So to elaborate on what I said before about making the SortProvider a parameter to the action.
Your links would change to something like this:
That way it will automatically map to the SortProvider's properties named "SortBy" and "IsDescending".
NOTE: This is only possible if the SortProvider has a default constructor (no parameters) otherwise model binding won't know how to instantiate the object.
It would appear you primarily do three things here. Sort, Filter, and Page. There's not much to say about your paging, but the sorting and filtering logic clutter the action. Try creating an object that does the sorting and filtering for you. Something like this:
Note: I didn't include all the logic you need, however; I did include what I believe to be enough logic for you to get the idea.
public interface ISortProvider
{
IQueryable Sort(IQueryable source);
}
// Purpose: Hold sorting criteria (column, direction) and sort a query of hardware based on held sorting criteria.
public class HardwareSortProvider : IFilterProvider
{
public string SortBy { get; set; }
public bool IsDescending { get; set; } // Defaults to false, AKA Ascending.
public IQueryable Sort(IQueryable source)
{
switch (SortBy)
{
case "serialNo":
return IsDescending
? source.OrderByDescending(h => h.SerialNo);
: source.OrderBy(h => h.SerialNo);
// ...
default:
return source;
}
}
}
public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
ViewBag.CurrentSort = sortOrder;
// ...
var hardwares = db.Hardwares; // Why mix query-style and extension-style? Just use one.
//
// SORT:
//
var sortProvider = new HardwareSortProvider();
// TODO: Set SortBy and IsDescending accordingly.
hardwares = sortProvider.Sort(hardwares);
//
// FILTER:
//
var filterProvider = new HardwareFilterProvider();
// ...
hardwares = filterProvider.Filter(hardwares);
// Execute and Page the query of Hardwares.
return View(hardwares.ToPagedList(pageNumber, pageSize));
}With this idea there are a few things you can do:
- Pass sort criteria in constructor.
- Add the sort provider to the action's parameter list and change your view's logic so that the SortBy and IsDescending automatically model bind.
- As alluded to in the Index action above, you can just as easily create a FilterProvider class/interface to do a similar effect.
Other Actions
The only other thing I see is that the following code is repeated in many of your actions.
ViewBag.Category_ID = new SelectList(db.categories, "CategoryID", "Type", hardware.Category_ID);
ViewBag.Employee_ID = new SelectList(db.employees, "EmployeeID", "FirstName", hardware.Employee_ID);
ViewBag.Model_ID = new SelectList(db.models, "CabinetModelID", "Name", hardware.Model_ID);
ViewBag.Platform_ID = new SelectList(db.platforms, "PlatformID", "Name", hardware.Platform_ID);
ViewBag.Vendor_ID = new SelectList(db.vendors, "VendorID", "Name", hardware.Vendor_ID);I commonly create a method to call this. The only slight caveat to this is if you ever need to change one of the actions calling it (not that common, but it happens sometimes).
View
So to elaborate on what I said before about making the SortProvider a parameter to the action.
public ViewResult Index(SortProvider sortProvider, string currentFilter, string searchString, int? page)
{
// ...
sortProvider.Sort(hardwares);
}Your links would change to something like this:
@Html.ActionLink("Serial No.", "Index", "Hardware", new RouteValueDictionary() { { "sortProvider.SortBy", "serialNo" }, { "sortProvider.IsDescending", SomeBooleanCondition.ToString() } }, null)That way it will automatically map to the SortProvider's properties named "SortBy" and "IsDescending".
NOTE: This is only possible if the SortProvider has a default constructor (no parameters) otherwise model binding won't know how to instantiate the object.
Code Snippets
public interface ISortProvider<T>
{
IQueryable<T> Sort(IQueryable<T> source);
}
// Purpose: Hold sorting criteria (column, direction) and sort a query of hardware based on held sorting criteria.
public class HardwareSortProvider : IFilterProvider<Hardware>
{
public string SortBy { get; set; }
public bool IsDescending { get; set; } // Defaults to false, AKA Ascending.
public IQueryable<Hardware> Sort(IQueryable<Hardware> source)
{
switch (SortBy)
{
case "serialNo":
return IsDescending
? source.OrderByDescending(h => h.SerialNo);
: source.OrderBy(h => h.SerialNo);
// ...
default:
return source;
}
}
}
public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
{
ViewBag.CurrentSort = sortOrder;
// ...
var hardwares = db.Hardwares; // Why mix query-style and extension-style? Just use one.
//
// SORT:
//
var sortProvider = new HardwareSortProvider();
// TODO: Set SortBy and IsDescending accordingly.
hardwares = sortProvider.Sort(hardwares);
//
// FILTER:
//
var filterProvider = new HardwareFilterProvider();
// ...
hardwares = filterProvider.Filter(hardwares);
// Execute and Page the query of Hardwares.
return View(hardwares.ToPagedList(pageNumber, pageSize));
}ViewBag.Category_ID = new SelectList(db.categories, "CategoryID", "Type", hardware.Category_ID);
ViewBag.Employee_ID = new SelectList(db.employees, "EmployeeID", "FirstName", hardware.Employee_ID);
ViewBag.Model_ID = new SelectList(db.models, "CabinetModelID", "Name", hardware.Model_ID);
ViewBag.Platform_ID = new SelectList(db.platforms, "PlatformID", "Name", hardware.Platform_ID);
ViewBag.Vendor_ID = new SelectList(db.vendors, "VendorID", "Name", hardware.Vendor_ID);public ViewResult Index(SortProvider sortProvider, string currentFilter, string searchString, int? page)
{
// ...
sortProvider.Sort(hardwares);
}@Html.ActionLink("Serial No.", "Index", "Hardware", new RouteValueDictionary() { { "sortProvider.SortBy", "serialNo" }, { "sortProvider.IsDescending", SomeBooleanCondition.ToString() } }, null)Context
StackExchange Code Review Q#132765, answer score: 7
Revisions (0)
No revisions yet.