I've created a partial view named "_ColorWheel.cshtml" which a string that holds the HEX value of the color.
This is the base model
public class PageComponentModel
{
// .....
public string BgColorVal { get; set; }
}
This model inherits PageComponentModel
public class OneColumnComponentModel: PageComponentModel
{
// ....
}
On their respective views, I use the same line #Html.Partial("_ColorWheel",Model.BgColorVal) to create the control. The view of OneColumnComponentModel works well, but PageComponentModel's throws
The model item passed into the dictionary is of type '.....Model.PageComponentModel', but this dictionary requires a model item of type 'System.String'.
I've tried multiple times to copy and paste the line just in case of typos or whatnot. Setting a breakpoint in razor shows that BgColorVal is string and has the correct value.
Related
Is there an "easy way" to ignore a property from Model serialization on a collection of a type in Asp.Net Core?
For example
public sealed class MainViewModel
{
public Guid Id { get; set; }
[JsonIgnore("PropertyInSubViewModel")]
public ICollection<SubViewModel> Products { get; set; }
}
The idea was to remove some property in SubViewModel from model serialization, so when I get it in my action, it would have its default value set, not the one set through the request.
Not with JsonIgnore. That can only be applied on the actual property that you want to ignore, and is constant at that point. However, JSON.NET does have support for conditional serialization. The easiest and most straight-forward is adding a ShouldSerialize* method to your class. You'd obviously need to determine some condition you could lean on for the determination, but that could be a straight-forward as literally setting some boolean on your sub view model instances. Basically, you just add something like:
public class SubViewModel
{
...
public bool ShouldSerializePropertyInSubViewModel()
{
// return true or false to either allow or disallow serializing the property on this instance
}
}
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)
I have a viewmodel as such
public class NoteViewModel
{
public tblNotes tblnote { get; set; }
}
In my controller, I do the following next after doing a build so my controller knows about the viewmodel:
NoteViewModel viewModel= new NoteViewModel();
viewModel.tblnote.NoteModeID = 1234; // get error here
return PartialView(viewModel);
I get the following error though:
{"Object reference not set to an instance of an object."}
What is the type tblNotes? (Side note: In C# class names should begin with a capital letter as a matter of convention.)
Since this is a custom type and, thus, a reference type, its default value is null. So when you instantiate a new NoteViewModel it's going to set all of its members to their default values unless otherwise specified. Since that value is null, you can't use it here:
viewModel.tblnote.NoteModeID = 1234;
Without knowing more about your types, the simple answer is to just instantiate that member in the view model's constructor:
public class NoteViewModel
{
public tblNotes tblnote { get; set; }
public NoteViewModel()
{
tblnote = new tblNotes();
}
}
This way the object will be instantiated any time a view model is created, so you can use it.
What exactly is tblNotes? If it is a reference type, than viewModel.tblNote is null after the first line of code is executed.
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.
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
}
}