Entity Framework ASP.NET MVC private model fields - asp.net-mvc

There is a field in our database which really ought to be a boolean, but for some reason the original developers made it a CHAR which will either be set to "1" or "0".
[Column("CHARGEABLE")]
[StringLength(1)]
private string Chargeable { get; set; }
I want my model to represent this field as a boolean so I figured I could add a property to my model to wrap it:
[NotMapped]
public bool ChargeableTrue
{
get
{
return Chargeable == "1" ? true : false;
}
set
{
Chargeable = value ? "1" : "0";
}
}
Now on my View I just display the EditorFor ( ChargeableTrue ), but when I click save it doesn't actually update it.
I think what is happening is that when the model is being updated, it's still attempting to get the value of 'Chargeable' from the View, even though I haven't displayed it there. And since there is no input field, it just gets null and ends up saving that to the database.
if (ModelState.IsValid)
{
db.Entry(call).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
What is one expected to do in this situation?

Based on KMan's answer, here's the extended version just in case you're not familiar with creating view models.
The idea is that your domain object is not really what you want to be updating exactly from your views. Instead, you create a go-between that can also include view-specific items (like a list of objects to populate a drop-down).
public class MyViewModel {
public bool Chargeable { get; set; }
}
Now you can do this:
#* In view *#
Html.EditorFor(m => m.Chargeable)
// In controller
public ActionResult Save(MyViewModel model) {
if (ModelState.IsValid) {
var domainObject = new MyObject() {
Chargeable = model.Chargeable ? "1" : "0"
};
// the rest of your code using domainObject
}
}
I'd consider just creating an overload of your domain object's constructor that accepts your view model to keep the mapping in one place. I typically use a tool like AutoMapper to map objects or manual extension methods.
A view model typically contains a sub-set of your domain object's properties, but can contain all of them or more properties like lists, visbility states, etc. They come in incredibly useful and I've never done a MVC project where I haven't used them.

Use a view model and make your mapping on the controller.

Related

Avoid to show Null or specific values to razor view engine

I am working on asp.net mvc3 web application using MS Sql server 2008 express rc2. In my app I have two different brands in DB and one of them have few Null or 'unknown' values (e.g. 'unknown' is added to DB instead of Null). My question is how to pass only non null values to View Engine instead of using If/Else statements in View?
in controller:
var model = _data.GetViewModel(query);
if (model != null)
{
return View(model);
}
else
return View("Error");
in ViewModel;
public int Id { get; set; }
public string Query { get; set; }
public string Brand { get; set; }
public string Family { get; set; }
public string Type { get; set; }
in Model:
public ViewModel GetViewModel(string query)
{
var data = _comp.Get(p => p.Query == query);
if (data == null) return null;
return new ViewModel
{
Id = data.id,
Brand = data.brand,
Family = data.family,
Type = data.type
};
}
in View (I am currently using If statement):
#if (Model.Brand != null)
{
<span class="brand">#Model.Brand</span>
}
#if (Model.Family != null)
{
<span class="family">#Model.Family</span>
}
#if (Model.Type != null)
{
<span class="type">#Model.Type</span>
}
Note: I want to avoid If statement because there are too many values in the Database of each brand, and many of the them are Null, So I don't want to generate Html for those Null values. I am using If/Else statement like above code, and for checking too many values in View using If, it costs Memory on server and processor, and it also slow down server response time.
I want to have an alternative method to do this. Should I use Partial views or anything else?
Please Please help me to solve this, Your help is very appreciated.
Thanks and Regards.
First, some background/context, then my suggestion.
(By the way, this all applies to any version of ASP.NET MVC or ASP.NET NancyFX (yes, there's another option out there!!), etc)
Context / Background
To solve this, people generally fall into two types of categories:
Just get data and let the View decide what to show (common one, not the proper way IMO).
The Controller should handle all the heavy lifting and give the view the exact answer (proper way, IMO).
The first way is quick and dirty. Sure it works, but it puts too much logic into the view. Views are not supposed to do any logic at all (exception: for loops, and maybe the odd if/else, maybe). The main reason for this is testing. Yep, that dirty word which people hate and think it's for hippies only. Or .. I don't have the time to test.. so I manually test, etc.. If you put any business logic into a view, you cannot test that.
The second way might seem a bit slower at first, but that's like anything - the more you practice, the faster you go. This is (IMO) the preferred method of doing things because you can test the controller. The controller should create a view model which will have -the exact- results that the view needs. Not extra. For example, imagine you want to return a list of Brands to the display/view. Most people do (the equivalent of) Get-all-brands into a list, and send that list to the view, even though 80% of those properties are -not- going to be used by that view! Even if ONE property is not going to be used by that view, do not retrieve it nor send it to the view!
So - TL;DR; do all the heavy lifting in the controller. The View is dumb. Just dump the exact view model data, to the view.
Solution to your problem
Ok, so let's roll with idea #2 and get all this stuff happening in the controller.
// Grab the results.
// ASSUMPTION: It is only returning the -exact- data I need. No more, no less.
var results = _data.GetViewModel(query);
if (model == null)
{
// Project the results into a perfectly tight & svelte view model
// 100% specific for this view.
var viewModel = results.
Select(x => new ViewModel
{
Id = x.Id,
Brand = string.IsNullOrEmpty(x.Brand)
? string.Empty
: x.Brand,
Family = string.IsNullOrEmpty(x.Family)
? string.Empty
: x.Family,
Type = string.IsNullOrEmpty(x.Type)
? string.Empty
: x.Type,
}).ToList();
return viewModel;
Testing this..
[Fact]
public void GivenSomeBrands_Index_ReturnsAViewModel()
{
// Arrange.
// NOTE: Our fake repostitory has some fake data. In it ..
// Id: 1, Brand: Gucci.
// Id: 22, Brand: null.
var controller = new BrandController(SomeFakeRepositoryThingy);
// Act.
var result = controller.Index(); // This calls that controller code, above.
// Assert.
Assert.IsNotNull(result); // Controller returned some result.
Assert.IsNotNull(result.Model); // We have some model data.
var model = result.Model as IList<ViewModel>(); // Cast the Model value.
Assert.NotNull(model); // We have a strongly typed view model.
// We check the first brand value.
Assert.Equal("Gucci", model.First().Brand);
// We know this item has a null Brand,
Assert.Equal(string.Empty, model[21].Brand); but the ViewModel converted it.
}
You could write a custom HTML helper:
public static string MyHelper<V>(this HtmlHelper helper, V value, string css)
{
if (value == null)
return "";
return String.Format("<span class='{0}'>{1}</span>", value, css);
}
Then in your view:
#Html.MyHelper(Model.Brand, "brand");
#Html.MyHelper(Model.Family, "family");
#Html.MyHelper(Model.Type, "type");

Should ViewModels have Count properties to simplify the View logic?

Which approach to ViewModels is better:
1. Just have an ICollection<T> in the ViewModel and access it's properties in views, which is pretty much what I'd do in ASP.NET Forms, like this:
public ICollection<Order> Orders {get; set;}
so that in the View I would do something like this
#if(Model.Orders.Count > 0){
or
2. Create a property for the Count of an ICollection so the View can simply reference that value directly.
public ICollection<Order> Orders { get; set; }
public int OrderCount { get { return Orders.Count ; } }
and then in the view
#if(Model.OrderCount > 0) {
or perhaps a boolean property HasOrders to further reduce the logic in the View?
Edit
I'm a bit surprised by the comments, I accept that this is subjective, but then so are questions about whether to use a string property for a date and everyone has to start learning somewhere.
Will I have numerous uses of the OrderCount property? The if and then a label to display the actual count. As such it will be used more frequently than say the customer email address yet I would be astonished if anyone suggested that
public string Email { get; set; }
was taking things too far.
To try to refocus the question a little; what I'm trying to determine is should the ViewModel provide simple properties for everything the view needs - so there is no need to reach down into the Model.Orders to access the Count. Should the View be kept pure and free from logic / 'programming'
3.) Don't use a Collection<T> on a viewmodel, it's probably overkill. Instead, use T[]. Why? Because you shouldn't need .Add, .Remove, and other overhead methods offered by ICollection for an IEnumerable property in a viewmodel. In the end, if you are just using it as a DTO to pass data from a controller to a view, an array is perfectly fine. Nothing will have to be added to or removed from the enumerable during transit to and from the controller. Arrays are generally faster and leaner than Lists and other IEnumerable implementations.
public Order[] Orders { get; set; }
Then, don't use .Count, use .Length. Having a separate property is usually overkill too IMO. Why? Because it just means you end up writing more code where you don't have to. Why add an OrdersCount property when you can just use Orders.Length?
#if (Model.Orders.Length > 0) {
If you are looking for something a little shorter, you can use the .Any() LINQ extension method (note you will have to have using System.Linq; when using this in a viewmodel class, but nothing extra should be needed to use it in a razor view):
#if (Model.Orders.Any()) { // returns true if Model.Orders.Length > 0
One possible exception to this guideline could be if Orders is not set, meaning it is null. In that case, your razor code above would throw a NullReferenceException. For this you could create a HasOrders property on the viewmodel to test against null and .Length. However a simpler solution could be to just initialize the property in a constructor:
public class MyViewModel
{
public MyViewModel()
{
Orders = new Order[0];
}
public Order[] Orders { get; set; }
}
Granted, with the above someone could still set the array to null, so it's your decision of whether to do this, or create a separate property to test against null, or just test against null in your razor code.
using System.Linq;
public class MyViewModel
{
public Order[] Orders { get; set; }
public bool HasOrders { get { return Orders != null && Orders.Any(); } }
}
...or...
#if (Model.Orders != null && Model.Orders.Any()) {
Any way you go, you end up with a little more code in either the consuming class or the consumed class. Use these factors to decide which approach means less code to write:
a.) Is it possible for the property to be null?
b.) How many collection properties are in the viewmodel?
c.) How many times do you have to test against either null or .Length in a razor view?

Shielding nullable domain properties with ViewModel

I am using Entity Framework 4.0, and making use of POCO objects. When I populate POCO objects from the DB, I translate property values to my own Domain objects, which we can call my Model.
Necessarily, whether or not the fields of my Model are Nullable depends on whether the value it maps to in the database comes from a NULL or NOT NULL column. I won't go into detail, but the values must be nullable in the DB, because a user can partially save a draft of the object before publishing it to the public. That being the case, I have several fields that are nullable. So let's say my model looks like:
public class MyModel
{
public int? Field1 {get; set; }
public DateTime? Field2 {get; set; }
public int Field3 {get; set; }
}
If I use this Model in my View, complete with nullable fields, I begin receiving errors that tell me I cannot use nullable properties as values in various places, like HTML helpers, etc. I could say something like if (Model.MyBoolField.HasValue && Model.MyBoolField.Value) { // etc }, but that feels bulky for a view.
I considered creating a ViewModel object that inherits from my original domain object and has new, non-nullable versions of my nullable fields that return an appropriate value if the base version is null. So something like:
public class MyViewModel : MyModel
{
public new int Field1
{
get { return base.Field1 ?? 7; }
}
public new DateTime Field2
{
get { return base.Field2 ?? DateTime.Now; }
}
}
My problem with this is that I don't always know a good "default" value to display. What if I threw an exception in the View Model's getter when the base value is null? Is that poor practice?
I'm basically looking for a best practice on how to handle nullable fields in a model, particularly when displaying in a View.
If you just need to display these fields in a View, you don't need to specify or check whether is has a value or not.
Using Model.Field1 in your View file is enough. It will simple not display anything, and it won't throw an exception. You can always use ?? to set a default when it makes sense.
#(Model.Field1 ?? "There is nothing to see here")
In most of the cases I use the "For" helpers, which seem OK with Nullable values (PublishedCount is a nullable property):
#Html.TextBoxFor(m => m.BillPull.PublishedCount, new { id="txtPublishedCount" })
#Html.ValidationMessageFor(m => m.BillPull.PublishedCount)
When I need to use just TextBox, I use the GetValueOrDefault method, with whatever default value the framework provides:
#Html.TextBox("BillPull.AutoPublishDate", Model.BillPull.AutoPublishDate.GetValueOrDefault().ToString(dateFormat), new { id = "dtpAutoPublishDate" })
#Html.ValidationMessageFor(m => m.BillPull.AutoPublishDate)

Want to save selected (i.e., more than 1) enums as string with NHibernate

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.

Asp.net MVC Model for view and Layout

I've been trying to find a good way to handle the Models of our Asp.net MVC websites when having common properties for all the pages. These properties are to be displayed in the Layout (Master Page). I'm using a "BaseModel" class that holds those properties and my Layout use this BaseModel as its model.
Every other model inherits from that BaseModel and each has specific properties relative to the view it represents. As you might have guessed, my Models are actually View Models even if that's not quite relevant here.
I have tried different ways to initialize the BaseModel values
By "hand" in every view
Having a base controller that has an Initialize virtual method to do it (so specific controller can implement specific common behavior for exemple)
Having a base controlelr that override OnActionExecuting to call the Initialize method
Using a helper class to do it outside of the controller
Using a Model Factory
But none of those really appeal to me:
Seems obvious to me, but DRY is one reason enough to justify that (actually I never tried that solution at all, I'm just putting it to be able to loop on that point in the last point).
I don't like that one because it means that whenever a new Controller is added, you need to know that it has to inherit from the BaseController and that you need to call the Initialize method, not to mention that if your controller has overriden the base one, to call the base anyway to maintain the values.
see next point
and 3. are a variation on the same topic but that doesn't really help with the issues of the second solution.
My favorite so far, but now I have to pass a few more variables to set those values. I like it for the inversion of dependence. But then if I want to provide values from the session, I need to pass them explicitly for exemple, then I'm back to square one as I have to provide them by hand (being references or through an interface of any kind)
Of course, (almost) all of those solutions work, but I'm looking for a better way to do it.
While typing this question, I found maybe a new path, the builder pattern that might also do, but implementations can become quickly a burden too, as we can have dozens of views and controllers.
I'll gladly take any serious recommandation/hint/advice/patterns/suggestion !
Update
Thanks to #EBarr I came up with another solution, using an ActionFilterAttribute (not production code, did it in 5 minutes):
public class ModelAttribute : ActionFilterAttribute
{
public Type ModelType { get; private set; }
public ModelAttribute(string typeName) : this(Type.GetType(typeName)) { }
public ModelAttribute(Type modelType)
{
if(modelType == null) { throw new ArgumentNullException("modelType"); }
ModelType = modelType;
if (!typeof(BaseModel).IsAssignableFrom(ModelType))
{
throw new ArgumentException("model type should inherit BaseModel");
}
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = ModelFactory.GetModel(ModelType);
var foo = filterContext.RequestContext.HttpContext.Session["foo"] as Foo;
model.Foo = foo;
model.Bar = somevalue;
filterContext.Controller.TempData["model"] = model;
}
}
Calling it is then really simple:
[Model(typeof(HomeModel))]
public ActionResult Index()
{
var homeModel = TempData["model"] as HomeModel;
// Add View Specific stuff
return View(homeModel);
}
And it gives me the best of every world. The only drawback is to find a proper way to passe the model back to the action.
Here it's done using the TempData object, but I also consider updating the model that one can find in the ActionParameters.
I'm still taking any serious recommandation/hint/advice/patterns/suggestion for that, or the previous points.
I went through almost exactly the same process as I dove into MVC. And you're right, none of the solutions feel that great.
In the end I used a series of base models. For various reasons I had a few different types of base models, but the logic should apply to a single base type. The majority of my view models then inherited from one of the bases. Then, depending on need/timing i fill the base portion of the model in ActionExecuting or OnActionExecuted.
A snippet of my code that should make the process clear:
if (filterContext.ActionParameters.ContainsKey("model")) {
var tempModel = (System.Object)filterContext.ActionParameters["model"];
if (typeof(BaseModel_SuperLight).IsAssignableFrom(tempModel.GetType())) {
//do stuff required by light weight model
}
if (typeof(BaseModel_RegularWeight).IsAssignableFrom(tempModel.GetType())) {
//do more costly stuff for regular weight model here
}
}
In the end my pattern didn't feel too satisfying. It was, however, practical, flexible and easy to implement varying levels of inheritance. I was also able to inject pre or post controller execution, which mattered a lot in my case. Hope this helps.
The idea that gave me #EBarr to use an action filter was actually working but felt wrong in the end, because there was no clean way to retrieve the model without passing through a viewbag, or the httpcontext items, or something alike. Also, it made mandatory to decorate every action with its model. It also made the postback more difficult to handle. I still believe that this solution has merits and might be useful in some specific scenarios.
So I was back to square one and started looking more into that topic. I came to the following. First the problem has two aspects
Initializing the data for the views
Rendering the data
While looking for more idea, I realized that I was not looking at the problem from the right perspective. I was looking at it from a "Controller" POV, whereas the final client for the model is the view. I was also reminded that the Layout/Master page is not a view and should not have a model associated with it. That insight put me on what feels the right path for me. Because it meant that every "dynamic" part of the Layout should be handled outside of it. Of course, sections seems the perfect fit for that, because of their flexibility.
On the test solution I made, I had (only) 4 different sections, some mandatory, some not. The problem with sections, is that you need to add them on every page, which can quickly be a pain to update/modify. To solve that, I tried this:
public interface IViewModel
{
KeyValuePair<string, PartialViewData>[] Sections { get; }
}
public class PartialViewData
{
public string PartialViewName { get; set; }
public object PartialViewModel { get; set; }
public ViewDataDictionary ViewData { get; set; }
}
For exemple, my model for the view is this:
public class HomeViewModel : IViewModel
{
public Article[] Articles { get; set; } // Article is just a dummy class
public string QuickContactMessage { get; set; } // just here to try things
public HomeViewModel() { Articles = new Article[0]; }
private Dictionary<string, PartialViewData> _Sections = new Dictionary<string, PartialViewData>();
public KeyValuePair<string, PartialViewData>[] Sections
{
get { return _Sections.ToArray(); }
set { _Sections = value.ToDictionary(item => item.Key, item => item.Value); }
}
}
This get initialized in the action:
public ActionResult Index()
{
var hvm = ModelFactory.Get<HomeViewModel>(); // Does not much, basicaly a new HomeViewModel();
hvm.Sections = LayoutHelper.GetCommonSections().ToArray(); // more on this just after
hvm.Articles = ArticlesProvider.GetArticles(); // ArticlesProvider could support DI
return View(hvm);
}
LayoutHelper is a property on the controller (which could be DI'ed if needed):
public class DefaultLayoutHelper
{
private Controller Controller;
public DefaultLayoutHelper(Controller controller) { Controller = controller; }
public Dictionary<string, PartialViewData> GetCommonSections(QuickContactModel quickContactModel = null)
{
var sections = new Dictionary<string, PartialViewData>();
// those calls were made in methods in the solution, I removed it to reduce the length of the answer
sections.Add("header",
Controller.UserLoggedIn() // simple extension that check if there is a user logged in
? new PartialViewData { PartialViewName = "HeaderLoggedIn", PartialViewModel = new HeaderLoggedInViewModel { Username = "Bishop" } }
: new PartialViewData { PartialViewName = "HeaderNotLoggedIn", PartialViewModel = new HeaderLoggedOutViewModel() });
sections.Add("quotes", new PartialViewData { PartialViewName = "Quotes" });
sections.Add("quickcontact", new PartialViewData { PartialViewName = "QuickContactForm", PartialViewModel = model ?? new QuickContactModel() });
return sections;
}
}
And in the views (.cshtml):
#section quotes { #{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "quotes").Value); } }
#section login { #{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "header").Value); } }
#section footer { #{ Html.RenderPartial(Model.Sections.FirstOrDefault(s => s.Key == "footer").Value); } }
The actual solution has more code, I tried to simplify to just get the idea here. It's still a bit raw and need polishing/error handling, but with that I can define in my action, what the sections will be, what model they will use and so on. It can be easily tested and setting up DI should not be an issue.
I still have to duplicate the #section lines in every view, which seems a bit painful (especialy because we can't put the sections in a partial view).
I'm looking into the templated razor delegates to see if that could not replace the sections.

Resources