I have built a simple MVC3-based ticket entry site for a less-than-usable call center application and am attempting to refactor my prototype to better adhere to design patterns partly to make it more maintainable going forward but mostly as a learning exercise.
The user-facing view is a form consisting of basic user information in addition to a few panels allowing selection of various resource types. Each resource type (hardware, software, etc) is displayed in the same way: using dual, filterable listboxes with add/remove buttons, an optional “justification” textarea that conditionally displays if a requested resource requires justification, and general comments.
I have built the following ViewModel for the individual panels:
public class RequestableList
{
// list of requestable items ids requiring justification
private List<string> _restrictedItems = new List<string>();
public List<string> RestrictedItems
{
get { return _restrictedItems; }
set { _restrictedItems = value; }
}
// the key-value pairs from which to populate available items list
private Dictionary<string, string> _availableItems = new Dictionary<string, string>();
public Dictionary<string, string> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
// item ids requested by user
private List<string> _requestedItems = new List<string>();
public List<string> RequestedItems
{
get { return _requestedItems; }
set { _requestedItems = value; }
}
}
The main ViewModel is then comprised of multiple RequestableLists as necessary:
public class SimpleRequestViewModel
{
public UserInfo userInfo { get; set; }
public RequestableList Software {get;set;}
public RequestableList Hardware {get;set;}
public RequestableList Access {get;set;}
public string SoftwareAdditionalInfo { get; set; }
public string HardwareAdditionalInfo { get; set; }
public string AccessFileMailShare { get; set; }
public string AccessAdditionalInfo { get; set; }
public string SoftwareJustification { get; set; }
public string HardwareJustification { get; set; }
public string AccessJustification { get; set; }
public string Comment { get; set; }
}
I have created a strongly typed view for SimpleRequestViewModel (and its variant) and a strongly typed EditorTemplate for RequestableList that wires up the dual listboxes, filtering, and jquery. All renders well and is working but the code currently smells.
When posting to the controller, if the model is valid I must translate it into a readable text description in order to create a new ticket in in the call center app. It doesn’t feel right to have the controller performing that translation into readable text but I run into hurdles when trying to design another class to translate the viewmodels.
Only the selected item values are posted so before translating the request into text I must first lookup the appropriate text for the provided values (they are required in description). The controller is currently the only object that has access to the call center data model for this lookup query.
There are 2 similar ViewModels containing varying combinations of RequestableLists so any translator must be able to translate the various combinations. One has only Hardware and Software, another may have Hardware Software, and a few more RequestableLists.
I considered overriding ToString() directly in the ViewModel but didn’t like that business logic (conditional rendering) there, and again, once posted, the ViewModel doesn’t contain the text for the selected items in the listbox so it would need access to the data model.
The translation of posted values to text as it is currently handled in the controller smells as it’s handled in a switch statement. The controller takes each posted RequestableList and populates the original “Available” fields before it builds the new ticket description.
switch (requestCategory)
{
case RequestableCategory.Software:
itemList = sde.GetSoftware();
break;
case RequestableCategory.Hardware:
itemList = sde.GetHardware();
break;
case RequestableCategory.Access:
itemList = sde.GetAccess();
break;
case RequestableCategory.Telecom:
itemList = sde.GetTelecom();
break;
default:
throw new ArgumentException();
}
So, my question(s):
What patterns are techniques would you recommend for performing the posted viewmodel to ticket description translation?
How do you typically handle the “only posts value” issue with select boxes when you need the text as well as the value?
Is there a better way for me to be approaching this problem?
Again, I am hoping this is a learning experience for me and am more than willing to provide additional information or description if needed.
A few suggestions:
Abstract the logic that does the call center submission into its own class. Provide (from the controller) whatever dependencies it needs to access the call center DB. Have different methods to handle the various types of view models using overloading. Presumably the descriptions come from the DB so you can extract the description from the DB based on the value in this class. This class could also take responsibility for building your view models for the display actions as well. Note that with this pattern the class can interact with the DB directly, through a repository, or even via web services/an API.
Use a repository pattern that implements some caching if performance is an issue in looking up the description from the DB the second time. I suspect it won't be unless your call center is very large, but that would be the place to optimize the query logic. The repository can be the thing that the controller passes to the submission class.
If you don't need to access the DB directly in the controller, consider passing the broker class as a dependency directly.
It might look like:
private ICallCenterBroker CallCenterBroker { get; set; }
public RequestController( ICallCenterBroker broker )
{
this.CallCenterBroker = broker;
// if not using DI, instantiate a new one
// this.CallCenterBroker = broker ?? new CallCenterBroker( new CallCenterRepository() );
}
[HttpGet]
public ActionResult CreateSimple()
{
var model = this.CallCenterBroker.CreateSimpleModel( this.User.Identity.Name );
return View( model );
}
[HttpPost]
public ActionResult CreateSimple( SimpleRequestViewModel request )
{
if (Model.IsValid)
{
var ticket = this.CallCenterBroker.CreateTicket( request );
// do something with ticket, perhaps create a different model for display?
this.CallCenterBroker.SubmitTicket( ticket );
return RedirectToAction( "index" ); // list all requests?
}
return View();
}
Related
Is there a way to use the DisplayAttribute values of an entity within a view model?
public partial class Catalog
{
[Display(ResourceType = typeof(Resources), Name = "ID")]
public string ID { get; set; }
[Display(ResourceType = typeof(Resources), Name = "CatalogName")]
public string CatalogName { get; set; }
}
public class CatalogViewModel
{
private readonly Catalog _catalog;
// I want reuse Catalog.CatalogName's display values.
public String CatalogName
{
get { return _catalog.CatalogName; }
}
}
I cannot access Resources from the view model.
Typically you wouldn't use domain objects within view models the way you do it here.
The explanation why is it not a good practice (although functionally it will work) is here.
What you would need to do is to define your view model from strings and ints (primitives)
and use something like AutoMapper to map between them. In real life web site it is rare that you will have domain model mapped one to one to view model, typically view model is tailored for specific view carrying all the required information from multiple tables.
Back to your question: having view model defined according to best practices I just described, you certainly can (and should) define all annotations and validations (using fluent validation or data annotation)
Hope this helps, please let me know if not.
I cannot for the life of me get this to work with my existing code, but I am trying to save my enum selections as strings in NHibernate. Basically, I have a UI check box and if the user selects multiple check boxes I want to store those selections. Now, I can get NHibernate to store ONE selection (e.g., from a drop down or radio button list, where the user is limited to one choice only).
This is the jist of what I have for an enum:
public enum IncomeType
{
[Display(Name = "Full-Time Employment")]
FullTime,
[Display(Name = "Part-Time Employment")]
PartTime,
[Display(Name = "Self-Employment")]
SelfEmployed,
[Display(Name = "Rental")]
Rental,
[Display(Name = "Social Security Payments")]
SocialSecurity,
[Display(Name = "Retirement / Pension Payments")]
Retirement,
[Display(Name = "Child Support Payments")]
ChildSupport,
[Display(Name = "Spousal Maintenance")]
Maintenance,
[Display(Name = "Other")]
Other
}
I use a method to "select" whether a checkbox list is shown (if my BulkItemThreshold equals the number of options, a checkbox list is displayed). Here is that method:
public static IEnumerable<SelectListItem> GetItemsFromEnumString<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
(Note: some items in there are helpers, but I don't believe they are relevant; also, the selected input is displayed using a template .cshtml file - again, not sure if that's relevant)
Now, I call this thusly:
public class IncomeTypeSelectorAttribute : SelectorAttribute
{
public override IEnumerable<SelectListItem> GetItems()
{
return Selector.GetItemsFromEnumString<IncomeType>();
}
}
And finally, we get to the virtual property (using a proxy) but this is where NHibernate throws a wrench (Note: this was working fine for me before NHibernate, and now I am trying to get many lines of code working with it WITHOUT having to re-do everything; if I re-do everything I will probably triple the code I already have to get it to work):
Property (record):
[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual List<string> IndividualIncomeTypeCheckBox { get; set; }
proxy (part):
public List<string> IndividualIncomeTypeCheckBox
{
get { return Record.IndividualIncomeTypeCheckBox; }
set { Record.IndividualIncomeTypeCheckBox = value; }
}
Again, this is how I was doing things and it was working great before NHibernate. But now I have to use NHibernate. No getting around it.
I am using a service class that it tying the two together in a Create method to save in the DB with NHibernate, and for the above it would ordinarily look like this:
part.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
This would work if it were just one selection.
Well, I've spent a good two (2) months trying to get this to work. It's tough because I have lots of code where the user can make only one selection (such as with a radiobutton list) and it works GREAT - even with NHibernate. Let me give you an example:
public virtual IncomeType? IndividualIncomeTypeCheckBox { get; set; }
If I do the above, it will display a drop down list, and NHibernate will store the ONE allowable option selected by the user in the DB no problem. But more than one option with List<string> does not work.
Now, I have tried everything I could find here or elsewhere and nothing works. Yes, I know it should be IList<IncomeType> or some other variant. But if I use this then NHibernate requires that IncomeType be another table in the DB. This is too much code to write for such a simple thing I believe. We are not talking a many-to-many relation in the sense that this is not a User with Multiple addresses (wherein addresses would have street, city, state, zip, etc.).
I have tried different types of proxy get and set code, but nothing works. I have tried [Flags] and other things working with string only, but to no avail. Those last solutions would "work" but ONLY to save the first item selected out of multiple (i.e., in my scenario, if the user selected "FullTime" and "Rental" as Income Types, then only "FullTime" (string) would be saved or "1" ([Flags]/int), not both items selected.
I have a situation where I re-display the choices using a ReadOnly attribute like this:
[IncomeTypeSelector]
[ReadOnly(true)]
public List<string> IndividualIncomeTypeCheckBoxPost
{
get { return IndividualIncomeTypeCheckBox; }
}
This would display on the UI, but I tried doing something like this with NHibernate and it wouldn't work.
Could anyone please show me, using the above, how I can go about getting NHibernate to store more than one enum in this checkbox list scenario?
UPDATE:
More poking around here and on the web, and I came up with the following (which still does not work).
Property (record):
[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{
get { return incomeType; }
set { incomeType= value; }
}
private IList<IncomeTypeRecord> incomeType =
new List<IncomeTypeRecord>();
Proxy (part):
public IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{
get { return Record.IndividualIncomeTypeCheckBox; }
set { Record.IndividualIncomeTypeCheckBox= value; }
}
And a change to the enum:
public enum IncomeType : int // removing int & value still gives validate error
{
[Display(Name = "Full-Time Employment")]
FullTime = 1,
[Display(Name = "Part-Time Employment")]
PartTime,
....
}
And I added this class to support IncomeTypeRecord
public class IncomeTypeRecord
{
public virtual int Id { get; set; }
public virtual IncomeType Value { get; set; }
}
HOWEVER, when I get to the UI screen and pick one or more options I get a validation error (value not valid). For example, say I pick FullTime alone, or pick FullTime and Retirement, then the UI will display the following error:
The value 'FullTime' is invalid.
The value 'FullTime,Retirement' is invalid.
(respectively)
Even if I remove the int declaration for the enum and get rid of the value I started with "1", I still get this validation error. I tried messing around with and adding different model binders (which now has me stumped as to whether my original problem still exists and now I have a different problem - but you still get bounty points :) ).
Pulling my hair out. If I could offer more bounty I would. I need a definitive solution. I appreciate any help.
UPDATE
This is what I have so far:
Record:
public virtual string IndividualIncomeTypeCheckBox{ get; set; }
Part:
//If I do IEnumberable<string> my .Select throws a cast error
public IEnumerable<IncomeType> IndividualIncomeTypeCheckBox
{
get
{
return Record
.IndividualIncomeTypeCheckBox
.Split(',')
.Select(r => (IncomeType)Enum.Parse(typeof(IncomeType), r));
}
set { Record.IndividualIncomeTypeCheckBox= value
== null ? null : String.Join(",", value); }
}
Service Class:
public SimplePart CreateSimple(SimplePartRecord record)
{
SimplePart simple = Services.ContentManager.Create<SimplePart>("Simple");
...
//How I would save a FirstName property (example Part / PartRecord below)
//public virtual string FirstName { get; set; } - PartRecord
//public string FirstName - Part
//{
// get { return Record.FirstName ; }
// set { Record.FirstName= value; }
//}
simple.FirstName = record.FristName;
...
//I obviously cannot do the following with the above IncomeType
//Getting cannot convert string to IEnumerable error
//How would I write this:
simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
...
}
And this is how it's called in a controller (this persists to DB): (Updating Controller code)
public ActionResult Confirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("WrapUp");
else if ((nextButton != null) && ModelState.IsValid)
{
_myService.CreateSimple(myData.SimplePartRecord);
return RedirectToAction("Submitted");
}
else
return View(myData);
}
Updating with additional code (serialization and view model):
"myData" is defined in the controller (using Serialization) as:
private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myData"];
if (serialized != null)
{
myData = (MyViewModel)new MvcSerializer().Deserialize
(serialized, SerializationMode.Signed);
TryUpdateModel(myData);
}
else
myData = (MyViewModel)TempData["myData"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myData"] = myData;
}
I use Serialization because I set up a multi-step wizard (as seen in the controller action "backButton" "nextButton) on the front-end. I am not using a driver (which can only display Admin or on the front-end but then only on .cshtml files directly under the ~/Views folder (not in a structured folder list like I am using)). No driver = no update view model type code = no mechanism to "create" the data in the DB. If I do not use some "create" type method, the form will submit but all the data will be "NULL".
When you say that the data should be persisted automatically, I am sorry but I do not see how. All the stuff I read or code I review has SOME method of updating the DB with whatever is entered in a form. If I am missing something, my apologies.
"MyViewModel" is pretty straightforward:
[Serializabel]
public class MyViewModel
{
public SimplePartRecord SimplePartRecord { get; set; }
}
And, just in case, here is the relevant portion of the migration (return 1 is a completely separate and unrelated table):
public int UpdateFrom1()
{
SchemaBuilder.CreateTable("SimplePartRecord",
table => table
.ContentPartRecord()
...
.Column("IndividualIncomeTypeCheckBox", DbType.String)
...
);
ContentDefinitionManager.AlterPartDefinition("SimplePart",
part => part
.Attachable(false));
return 2;
}
The error I am getting is
Cannot implicitly convert type 'string' to 'System.Collections.Generic.IEnumerable'"
when I do the following in the "Create" method of my service class:
simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
One additional thought: I tried using the n-n Relation sample to handle this scenario. Aside from it being a lot of extra code for what I thought should be straightforward and simple, because of the way I am using Serialization I had a lot of object reference errors and could not figure out how to properly code my controller to handle it.
There's a lot of info to wade through here so hopefully I haven't missed the point. It appears to me that the goals are:
The business class has a collection property of IList<IncomeType> without requiring an additional table
The values in that collection should be persisted as a delimited string of the enum names
The best approach is to use a custom user type (an implementation of NHibernate.UserTypes.IUserType) to map the property. Below is a generic IUserType that will map an enum of type T from an IList<T> property to a comma delimited string in the database and back again. There's no easy way to restrict T to an enum but the code will only work with enums.
Mapping a property using the custom type is simple with Fluent NHibernate:
public class Person
{
public Person()
{
IncomeTypes = new List<IncomeType>();
}
public virtual int PersonId { get; protected set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual IList<IncomeType> IncomeTypes { get; protected set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.PersonId).GeneratedBy.Identity();
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.IncomeTypes).CustomType<EnumAsDelimitedStringType<IncomeType>>();
}
}
And here's the code for the user type:
public class EnumAsDelimitedStringType<T> : IUserType
{
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xList = x as IList<T>;
var yList = y as IList<T>;
if (xList == null || yList == null)
{
return false;
}
// compare set contents
return xList.OrderBy(xValue => xValue).SequenceEqual(yList.OrderBy(yValue => yValue));
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.AnsiString.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new List<T>();
}
var getValueArray = outValue.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
return Array.ConvertAll(getValueArray, s => (T)Enum.Parse(typeof(T), s)).ToList();
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as IList<T>;
// set to string.Empty if you prefer to store that instead of null when the collection is null or empty
object setValue = null;
if (inValue != null && inValue.Any())
{
var setValueArray = Array.ConvertAll(inValue.ToArray(), v => Enum.GetName(typeof(T), v));
setValue = string.Join(",", setValueArray);
}
NHibernateUtil.AnsiString.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.AnsiString)}; }
}
public Type ReturnedType
{
get { return typeof(IList<T>); }
}
public bool IsMutable
{
get { return false; }
}
}
I think you're on the right track pursuing a [Flags] enum. You may have done this, but just in case -- making an enum flags-worthy is more than adding the attribute. You also have to specify the value for the items in a binary-friendly manner. I've found the easiest way to do this is as follows:
[Flags]
public enum IncomeType : long // you'll need the room with several options
{
FullTime = 1,
PartTime = 1 << 1,
SelfEmployed = 1 << 2
// And so on
}
If you don't do this, then you'll get sequential integer values, which breaks the bitwise comparison that allows you to do multiple values in a single integer.
Your code to create the SelectList looks fine. Your options should construct form values that get posted back with the same name. If you want to use the default modelbinder, that means the associated property on your view model would need to be List<int>. If you're not using a view model (you probably should) you can pull it out of the forms collection.
Once you have this set up, then translating from your view model to your NHibernate entity is simple if a little annoying. You basically have to cycle through the values in the list and |= them onto your NHibernate entity's single enum property.
So let's assume you have a view model like this:
public class MyEditViewModel
{
public string Name { get; set; }
public List<int> IncomeSelections { get; set; }
// You'll probably have this to populate the initial view rendering
public SelectList AllIncomeOptions { get; set; }
}
You'll build your view using your helpers and all that, then build the checkboxes using the SelectList but making sure the input name is IncomeSelections, then when it's posted back you will push the view model data into your NHibernate entity something like this:
var myNHEntity = new NHEntity();
// If you're editing an existing entity, then be sure to reset the enum
// value to 0 before going into the following foreach loop...
foreach (var incomeSelection in viewModel.IncomeSelections)
{
myNHEntity.IncomeSelection |= incomeSelection;
}
There's probably a more clever way to do this, and you might have to cast the int to your enum type, but you'll figure that out (I'd do it for you, but it is Friday and I already have a beer open).
NHibernate should persist it without you having to do anything funky on the NH side.
In summary...
It seems like this is more a problem of how you handle the posted data than the NHibernate side. If you implement something like this, then be sure to use Fiddler or FireBug to inspect the posted values to make sure 1) they're integers and 2) the names are the same so they'll be added to the list.
Good luck!
The problem is simply that it won't be able to map a List without building a full relationship with an intermediate association table. It is way simpler to have the record store the values as a comma-separated string (so your record property is a string, not a list of string) and your part can map back and forth between string and List.
You can find an example of something very close here:
https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPartRecord.cs?at=default
https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPart.cs?at=default
It's not using enum values, instead it's a list of ids, but that should give you a good idea about how to make this work fairly simply: parsing enums you already know how to do.
Let me know if you need more details, but I think that's what you needed to get unblocked.
I'm still learning, but with the stackoverflow commnuties help, I've been able to get closer and closer.
What I have right now is a View "Index.aspx":
System.Web.Mvc.ViewPage<Data.Models.GetDealsModel>
The Model:
public class GetDealsModel
{
// set up the model
public string DealId { get; set; }
public string StreetAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string Logo { get; set; }
public string Website { get; set; }
public string TotalRows { get; set; }
}
And the controller:
public ActionResult Index()
{
LinqToDealsDataContext db = new LinqToDealsDataContext();
XElement xmlTree = XElement.Parse("<Request><ZipCode>92612</ZipCode></Request>");
var deals = db.spSearchDeals(xmlTree);
return View(deals);
}
And with this configuration I'm now getting this error:
The model item passed into the dictionary is of type 'System.Data.Linq.SqlClient.SqlProvider+SingleResult`1[Data.Models.spSearchDealsResult]', but this dictionary requires a model item of type 'Data.Models.GetDealsModel'.
I'm guessing that there's an issue connecting my Controller to my Model... I'm not sure why. PLEASE help me connect this final peice.
NOTE: I do understand that eventually I should separate my logic in the controller into a Repository Pattern, but for now, this will do.
You need to translate the data coming back from this call:
var deals = db.spSearchDeals(xmlTree);
into a GetDealsModel type. So something like:
GetDealsModel dealsModel = new GetDealsModel()
{
DealId = deals.DealId,
StreetAddress = deals.StreetAddress,
....
};
return View(dealsModel);
The reason being that your View is strongly typed to take a GetDealsModel, but your deals variable is not of that type and it gives you that exception when you pass it to the View.
You should create object of type GetDealsModel, but your DB Query returns object of type Data.Models.spSearchDealsResult. Try something like:
return new GetDealsModel
{
DealId = deals.Id,
// other fields here
}
Add to your learning curve list the following items:
Repository Pattern
Ask yourself the following question: Why do I need a service layer?
Read Steven Sanderson's book. It teaches you to think in MVC.
The above applies to your problems because your issues are clearly related to having code in your Controllers that should be in your Model (ie, data access code should be in a repository class). Ie, you are not thinking in MVC.
Your model should include the necessary repository classes, eg, DealRepository.
You need a Service class to map the objects your repository digs out of your database to your model class: that way conversion problems are encapsulated into the Service Layer code.
If you do this, you can then write in your controller:
public ActionResult Index()
{
return(DealService.GetByZipcode(92612));
}
Where DealService.GetByZipcode basically just maps DealRepository.GetByZipcode(92612) to your model class and returns the mapping result.
The DealRepository.GetByZipcode method would be roughly:
public static DealEntity GetByZipcode(string zip)
{
LinqToDealsDataContext db = new LinqToDealsDataContext();
XElement xmlTree = XElement.Parse("<Request><ZipCode>" + zip + "</ZipCode></Request>");
var deals = db.spSearchDeals(xmlTree);
return deals;
}
The DealEntity class is just whatever Linq gives you for your table.
The reason WHY for all this:
The reason for this structure is as follows:
a. All you data access code is in one place: DealRepository. You can test and debug that independently of everything else.
b. The mapping code is all in one place: DealService. You can test and debug that independently of everything else.
c. In other words, you need to properly separate your concerns.
The problem with your existing code is precisely that you have NOT separated concerns. Ie, you have taken a dash of MVC and put it in a food processor and ended up with mush full of problems that are way more difficult to deal with than they need be.
Your model is mixed into your controller, there is no repository, no service layer.
So hold your horses just a while and take the time to read Steve Sanderson's book.
I would also try modelling a simpler problem. That xml parsing makes my head hurt even on a good day.
NOTE:
You could seriously improve your naming conventions. LinqToDealsDataContext? You're kidding, right?
Description
My solution has these projects:
DAL = Modified Entity Framework
DTO = Data Transfer objects that are able to validate themselves
BL = Business Layer Services
WEB = presentation Asp.net MVC application
DAL, BL and WEB all reference DTO which is great.
The process usually executes this way:
A web request is made to the WEB
WEB gets DTOs posted
DTOs get automagically validated via custom ActionFilter
validation errors are auto-collected
(Validation is OK) WEB calls into BL providing DTOs
BL calls into DAL by using DTOs (can either pass them through or just use them)
DTO Validation problem then...
My DTOs are able to validate themselves based on their own state (properties' values). But right now I'm presented with a problem when this is not the case. I need them to validate using BL (and consequently DAL).
My real-life example: User registers and WEB gets a User DTO that gets validated. The problematic part is username validation. Its uniqueness should be checked against data store.
How am I supposed to do this?
There's additional info that all DTOs implement an interface (ie. User DTO implements IUser) for IoC purposes and TDD. Both are part of the DTO project.
Impossible tries
I can't reference BL in DTO because I'll get circular reference.
Compilation error
I can't create an additional DTO.Val project that would reference partial DTO classes and implement their validation there (they'd reference BL + DTO).
Partial classes can't span assemblies.
Possible tries
Create a special ActionFilter that would validate object against external conditions. This one would be created within WEB project thus seeing DTO and BL that would be used here.
Put DTOs in BL and keep DTO interfaces as actual DTOs referenced by other projects and refactor all code to use interfaces instead of concrete classes.
Don't handle external dependant validation and let external dependencies throw an exception - probably the worst solution to this issue
What would you suggest?
I would suggest an experiment that i have only been trialling for the last week or so.
Based on this inspiration i am creating DTOs that validate a little differently to that of the DataAnnotations approach. Sample DTO:
public class Contact : DomainBase, IModelObject
{
public int ID { get; set; }
public string Name { get; set; }
public LazyList<ContactDetail> Details { get; set; }
public DateTime Updated { get; set; }
protected override void ConfigureRules()
{
base.AddRule(new ValidationRule()
{
Properties = new string[] { "name" },
Description = "A Name is required but must not exceed 300 characters in length and some special characters are not allowed",
validator = () => this.Name.IsRequired300LenNoSpecial()
});
base.AddRule(new ValidationRule()
{
Properties = new string[] { "updated" },
Description = "required",
validator = () => this.Updated.IsRequired()
});
}
}
This might look more work than DataAnnotations and well, that's coz it is, but it's not huge. I think it's more presentable in the class (i have some really ugly DTO classes now with DataAnnotations attributes - you can't even see the properties any more). And the power of anonymous delegates in this application is almost book-worthy (so i'm discovering).
Base class:
public partial class DomainBase : IDataErrorInfo
{
private IList<ValidationRule> _rules = new List<ValidationRule>();
public DomainBase()
{
// populate the _rules collection
this.ConfigureRules();
}
protected virtual void ConfigureRules()
{
// no rules if not overridden
}
protected void AddRule(ValidationRule rule)
{
this._rules.Add(rule);
}
#region IDataErrorInfo Members
public string Error
{
get { return String.Empty; } // Validation should call the indexer so return "" here
} // ..we dont need to support this property.
public string this[string columnName]
{
get
{
// get all the rules that apply to the property being validated
var rulesThatApply = this._rules
.Where(r => r.Properties.Contains(columnName));
// get a list of error messages from the rules
StringBuilder errorMessages = new StringBuilder();
foreach (ValidationRule rule in rulesThatApply)
if (!rule.validator.Invoke()) // if validator returns false then the rule is broken
if (errorMessages.ToString() == String.Empty)
errorMessages.Append(rule.Description);
else
errorMessages.AppendFormat("\r\n{0}", rule.Description);
return errorMessages.ToString();
}
}
#endregion
}
ValidationRule and my validation functions:
public class ValidationRule
{
public string[] Properties { get; set; }
public string Description { get; set; }
public Func<bool> validator { get; set; }
}
/// <summary>
/// These extention methods return true if the validation condition is met.
/// </summary>
public static class ValidationFunctions
{
#region IsRequired
public static bool IsRequired(this String str)
{
return !str.IsNullOrTrimEmpty();
}
public static bool IsRequired(this int num)
{
return num != 0;
}
public static bool IsRequired(this long num)
{
return num != 0;
}
public static bool IsRequired(this double num)
{
return num != 0;
}
public static bool IsRequired(this Decimal num)
{
return num != 0;
}
public static bool IsRequired(this DateTime date)
{
return date != DateTime.MinValue;
}
#endregion
#region String Lengths
public static bool IsLengthLessThanOrEqual(this String str, int length)
{
return str.Length <= length;
}
public static bool IsRequiredWithLengthLessThanOrEqual(this String str, int length)
{
return !str.IsNullOrTrimEmpty() && (str.Length <= length);
}
public static bool IsRequired300LenNoSpecial(this String str)
{
return !str.IsNullOrTrimEmpty() &&
str.RegexMatch(#"^[- \r\n\\\.!:*,#$%&""?\(\)\w']{1,300}$",
RegexOptions.Multiline) == str;
}
#endregion
}
If my code looks messy well that's because i've only been working on this validation approach for the last few days. I need this idea to meet a few requirements:
I need to support the IDataErrorInfo interface so my MVC layer validates automatically
I need to be able to support complex validation scenarios (the whole point of your question i guess): I want to be able to validate against multiple properties on the same object (ie. StartDate and FinishDate); properties from different/multiple/associated objects like i would have in an object graph; and even other things i haven't thought of yet.
I need to support the idea of an error applying to more than one property
As part of my TDD and DDD journey i want my Domain Objects to describe more my 'domain' than my Service layer methods, so putting these complex conditions in the model objects (not DTOs) seems to achieve this
This approach i think will get me what i want, and maybe you as well.
I'd imagine if you jump on board with me on this that we'd be pretty 'by ourselves' but it might be worth it. I was reading about the new validation capabilities in MVC 2 but it still doesn't meet the above wish list without custom modification.
Hope this helps.
The S#arp Architecture has an [DomainSignature] method identifier that used with the class level validator [HasUniqueDomainSignature] will do the work. See the sample code below:
[HasUniqueDomainSignature]
public class User : Entity
{
public User()
{
}
public User(string login, string email) : this()
{
Login = login;
Email = email;
}
[DomainSignature]
[NotNullNotEmpty]
public virtual string Login { get; set; }
[DomainSignature]
public virtual string Email { get; set; }
}
Take a closer look at http://www.sharparchitecture.net/
I had this exact same problem and after trying to find a work around for days and days and days, I ended up merging my DTO, DAL, and BL into one library. I kept my presentation layer separate.
Not sure if that is an option for you or not. For me, I figured that my chances of ever changing the data store were very slight, and so the separate tier wasn't really needed.
I also have implemented the Microsoft Validation Application Block for all my DTO validations. They have a "Self Validation" method that lets you perform complex validations.
Resulting solution
I ended up using controller action filter that was able to validate object against external factors that can't be obtained from the object itself.
I created the filter that takes the name of the action parameter to check and validator type that will validate that particular parameter. Of course this validator has to implement certain interface to make it all reusable.
[ValidateExternalFactors("user", typeof(UserExternalValidator))]
public ActionResult Create(User user)
validator needs to implement this simple interface
public interface IExternalValidator<T>
{
bool IsValid(T instance);
}
It's a simple and effective solution to a seemingly complex problem.
The project I'm working on has a large number of currency properties in the domain model and I'm needing for format these as $#,###.## for transmitting to and from the view. I've had a view thoughts as to different approaches which could be used. One approach could be to format the values explicitly inside the view, as in "Pattern 1" from Steve Michelotti :
<%= string.Format("{0:c}",
Model.CurrencyProperty) %>
...but this starts violating DRY principle very quickly.
The preferred approach appears to be to do the formatting during the mapping between DomainModel and a ViewModel (as per ASP.NET MVC in Action section 4.4.1 and "Pattern 3"). Using AutoMapper, this will result in some code like the following:
[TestFixture]
public class ViewModelTests
{
[Test]
public void DomainModelMapsToViewModel()
{
var domainModel = new DomainModel {CurrencyProperty = 19.95m};
var viewModel = new ViewModel(domainModel);
Assert.That(viewModel.CurrencyProperty, Is.EqualTo("$19.95"));
}
}
public class DomainModel
{
public decimal CurrencyProperty { get; set; }
}
public class ViewModel
{
///<summary>Currency Property - formatted as $#,###.##</summary>
public string CurrencyProperty { get; set; }
///<summary>Setup mapping between domain and view model</summary>
static ViewModel()
{
// map dm to vm
Mapper.CreateMap<DomainModel, ViewModel>()
.ForMember(vm => vm.CurrencyProperty, mc => mc.AddFormatter<CurrencyFormatter>());
}
/// <summary> Creates the view model from the domain model.</summary>
public ViewModel(DomainModel domainModel)
{
Mapper.Map(domainModel, this);
}
public ViewModel() { }
}
public class CurrencyFormatter : IValueFormatter
{
///<summary>Formats source value as currency</summary>
public string FormatValue(ResolutionContext context)
{
return string.Format(CultureInfo.CurrentCulture, "{0:c}", context.SourceValue);
}
}
Using IValueFormatter this way works great. Now, how to map it back from the DomainModel to ViewModel? I've tried using a custom class CurrencyResolver : ValueResolver<string,decimal>
public class CurrencyResolver : ValueResolver<string, decimal>
{
///<summary>Parses source value as currency</summary>
protected override decimal ResolveCore(string source)
{
return decimal.Parse(source, NumberStyles.Currency, CultureInfo.CurrentCulture);
}
}
And then mapped it with:
// from vm to dm
Mapper.CreateMap<ViewModel, DomainModel>()
.ForMember(dm => dm.CurrencyProperty,
mc => mc
.ResolveUsing<CurrencyResolver>()
.FromMember(vm => vm.CurrencyProperty));
Which will satisfy this test:
///<summary>DomainModel maps to ViewModel</summary>
[Test]
public void ViewModelMapsToDomainModel()
{
var viewModel = new ViewModel {CurrencyProperty = "$19.95"};
var domainModel = new DomainModel();
Mapper.Map(viewModel, domainModel);
Assert.That(domainModel.CurrencyProperty, Is.EqualTo(19.95m));
}
... But I'm feeling that I shouldn't need to explicitly define which property it is being mapped from with FromMember after doing ResolveUsing since the properties have the same name - is there a better way to define this mapping? As I mentioned, there are a good number of properties with currency values that will need to be mapped in this fashion.
That being said - is there a way I could have these mappings automatically resolved by defining some rule globally? The ViewModel properties are already decorated with DataAnnotation attributes [DataType(DataType.Currency)] for validation, so I was hoping that I could define some rule that does:
if (destinationProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency))
then Mapper.Use<CurrencyFormatter>()
if (sourceProperty.PropertyInfo.Attributes.Has(DataType(DataType.Currency))
then Mapper.Use<CurrencyResolver>()
... so that I can minimize the amount of boilerplate setup for each of the object types.
I'm also interested in hearing of any alternate strategies for accomplishing custom formatting to-and-from the View.
From ASP.NET MVC in Action:
At first we might be tempted to pass
this simple object straight to the
view, but the DateTime? properties
[in the Model] will cause problems.
For instance, we need to choose a
formatting for them such as
ToShortDateString() or ToString(). The
view would be forced to do null
checking to keep the screen from
blowing up when the properties are
null. Views are difficult to unit
test, so we want to keep them as thin
as possible. Because the output of a
view is a string passed to the
response stream, we’ll only use
objects that are stringfriendly; that
is, objects that will never fail when
ToString() is called on them. The
ConferenceForm view model object is an
example of this. Notice in listing
4.14 that all of the properties are strings. We’ll have the dates properly
formatted before this view model
object is placed in view data. This
way, the view need not consider the
object, and it can format the
information properly.
Have you considered using an extension method to format money?
public static string ToMoney( this decimal source )
{
return string.Format( "{0:c}", source );
}
<%= Model.CurrencyProperty.ToMoney() %>
Since this is clearly a view-related (not model-related) issue, I'd try to keep it in the view if at all possible. This basically moves it to an extension method on decimal, but the usage is in the view. You could also do an HtmlHelper extension:
public static string FormatMoney( this HtmlHelper helper, decimal amount )
{
return string.Format( "{0:c}", amount );
}
<%= Html.FormatMoney( Model.CurrencyProperty ) %>
If you liked that style better. It is somewhat more View-related as it's an HtmlHelper extension.
Have you considered putting a DisplayFormat on your ViewModel? That is what I use and it's quick and simple.
ViewModel :
[DisplayFormat(DataFormatString = "{0:c}", ApplyFormatInEditMode = true)]
public decimal CurrencyProperty { get; set; }
View :
#Html.DisplayFor(m => m.CurrencyProperty)
A custom TypeConverter is what you're looking for:
Mapper.CreateMap<string, decimal>().ConvertUsing<MoneyToDecimalConverter>();
Then create the converter:
public class MoneyToDecimalConverter : TypeConverter<string, decimal>
{
protected override decimal ConvertCore(string source)
{
// magic here to convert from string to decimal
}
}