Conditional Calucations with MVC4 - asp.net-mvc-3

I am trying to conditionally calculate some values based on user inputs in a form. The "inputs" themselves tie into the calculation, in that if a user is presented with two radio button lists their selections will determine whether or not a currency amount is attached, and then later added together. I don't understand jquery, and so I cannot figure out how to use something like knockout.js to do what I want (besides, my calculation will be served up in a confirmation view only and will not be "re-calculated" unless the user goes back and changes their selections).
Note, I am not talking about the scenario where you have a textbox that accepts a value and adding it to another textbox value the user inputs (e.g., user enters 10, then 10, then form calculates 20). The values are static based upon selection.
For context, I am using Serializer and a wizard which is stepping through and passing model selections/inputs to the subsequent view.
I can do something like this as an example (it's simple, but based on responses I can figure out how to build it up to the more complex scenario I have):
Model.cs:
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal calculated { get; set; }
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal option1 = 500;
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal option2 = 100;
//Following is just my idea to calculate $0 if
//a selection results in a "no value option"
//although I suppose if nothing is selected I
//can just avoid calculating + $0
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal nooption = 0;
Then in Confirm.cshtml:
#{
Model.calculated = Model.option1 + Model.option2 + Model.nooption;
}
....
#Html.DisplayFor(m => m.calculated)
will display $600.00. That's easy enough.
However, my goal is threefold. First, I need to tie in, for example, option1 with the selection from a radiobutton list, and option2 with another radiobutton list depending on what the user selects. Second, depending on the selection I want to conditionally calculate all the options present which will obviously vary based on user selection (some users will select option1 and option2, others just option1, others just option2, still others neither). Third, where is the best place to have the calculation take place (the view, a .cs file, the controller (although I am trying to minimize code there), etc.).
So, if I have these enums for the radiobutton lists (each option corresponding to an option for now, later I will discuss two options per radiobutton list enum):
public enum radio1
{
radio1selection1, // this would correspond to nooption (or $0)
radio1selection2 // this would correspond to option1 (or $500)
}
public enum radio2
{
radio2selection1, // this would correspond to nooption (or $0)
radio2selection2 // this would correspond to option2 (or $100)
}
I've indicated in the enums how I'd like to "tie" the option to the selection.
My problem is in trying to do if statements, which I cannot figure out properly. Something like this in Confirm.cshtml:
#{
if (Model.radio1 == radio1selection1)
Model.calculated = Model.nooption;
else....
}
The above is completely wrong, I know. I am trying to think of all the permutations of calculating, but I don't think its worth the effort because it would either be wrong or it would work but be too long when there is an easier way. I was even thinking of trying case/break but that too seems oddly the wrong approach.
What's adding to the complexity for me is if my enums have more than two selections, and those other selections result in several options, say option1 and option1a, and depending on which the user selects will determine the calculation. So the enum becomes:
public enum radio1
{
radio1selection1, // this would correspond to nooption (or $0)
radio1selection2, // this would correspond to option1 (or $500)
radio1selection3 // this would correspond to NEW
// option1a (or $750) (with a corresponding
// "int" named "option1a")
}
One of the things I thought of doing was to just display all the options selected in a <table> in the view, so that even if something is a $0 value, it would just say $0 as the option. Then I thought I could just add all the values present. But still I don't know the best way to accomplish that.
Any opinions on how I would be able to and or all three of my stated goals above?

I would define the property as follows:
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal calculated { get { return option1 + option2 + option3; } }
You can make the logic as conditional as necessary inside the getter. I think model is the best place for those calculations, which is what we get with the calculated property.
To render a collection of options, define them as follows:
public class MyOption
{
public string ID { get; set; }
public string Name { get; set; }
}
List<MyOptions> options = new List<MyOptions>();
options.Add(new MyOption { ID = "1", Name = "First" });
model.Options = options;
You can have a method instead of the Options property that returns a list of options based on the data of the option* properties.
Then convert it to a SelectList object in some helper method. An HTML helper can be defined as follows:
public static class MyHelperExtensions
{
public static SelectList MyOptionList(this HtmlHelper helper, List<MyOptions> options)
{
return new SelectList(options, "ID", "Name");
}
}
In the view, use the following code to render the options:
#Html.DropDownListFor(x => x.MyOptionProperty, Html.MyOptionList(Model.Options))

I figured out how to do this (might not be clean/efficient/standard practice, but it got the job done):
Model.calculated =
Model.PriceQuote.priceChapter7Solution +
((Model.radiobuttonlist1 ==
Namespace.ViewModels.MyModel.radio1.radio1selection1) ?
Model.option1 :
(Model.radiobuttonlist1 ==
Namespace.ViewModels.MyModel.radio1.radio1selection2) ?
Model.option2 :
Model.nooption);
I could just add + after ... : Model.nooption) instead of ending the statement with ; to calculate conditions on other options.
Originally I did not know that the conditional (? :) operator could contain more than two expressions - i.e., it's right associative so additional conditions will be evaluated as a ? b : (c ? d : e). See Docs.

Related

Microsoft Bot: Show options from database instead of enums

In the example bot implementations from Microsoft, they use enums to define options for dialog, as shown in the example below:
public enum LengthOptions { SixInch, FootLong };
public enum BreadOptions { NineGrainWheat, NineGrainHoneyOat, Italian, ItalianHerbsAndCheese, Flatbread };
Can we use a normal list to fetch the values from the database and display it as options?
Thanks
You can't do this out of the box, but you could subclass FormBuilderBase<T>, overriding various methods to build the Form using whatever datasource you prefer.
Edit:
You can find the base class and implementation of FormBuilder here: https://github.com/Microsoft/BotBuilder/blob/master/CSharp/Library/FormFlow/FormBuilder.cs
Basically, there are a mess of virtual methods that you can override to customize how you want to form to behave, but the main one is Build. In the default implementation, it iterates though the enums to create a list of Field, which are basically each step in you form. Instead of that, you can iterate through whatever data you have pulled from your database and create a new Field for each item. It may look something like this:
public override IForm<T> Build(Assembly resourceAssembly = null, string resourceName = null)
{
var list = GetListOfItemsFromDatabase();
foreach (var item in _list)
{
// FieldFromItem is an IField and will also need to be created
Field(new FieldFormItem<T>(item));
}
Confirm(new PromptAttribute(_form.Configuration.Template(TemplateUsage.Confirmation)));
}
return base.Build(resourceAssembly, resourceName);
}
I know its late but found myself struggling with the same and found that below would be the right solution for this.In your FormFlow class just add the Terms and Descriptions manually.From your example if we are talking about length options then change the type of LengthOptions to string add following code when you build the form.
return new FormBuilder<SandwichForm>()
.Field(new FieldReflector<SandwichForm>(nameof(LengthOptions))
.SetDefine(async (state, field) =>
{
// Call database and get options and iterate over the options
field
.AddDescription("SixInch","Six Inch")
.AddTerms("SixInch", "Six Inch")
.AddDescription("FootLong ","Foot Long")
.AddTerms("FootLong ", "Foot Long")
return true;
}))
.OnCompletion(completionDelegate)
.Build();

How do I store a comma-separated list in Orchard CMS?

Using Orchard CMS, I am dealing with a record and a part proxy, but cannot figure out how to save it into the DB. In fact, I confess I don't even know how to get the items I'm trying to save into this paradigm. I was originally using enum's for choices:
MyEmum.cs:
public enum Choices { Choice1, Choice2, Choice3, Choice4 }
MyRecord.cs:
public virtual string MyProperty { get; set; }
MyPart.cs:
public IEnumerable<string> MyProperty
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyProperty)) return new string[] { };
return Record
.MyProperty
.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set { Record.MyProperty = value == null ? null : String.Join(",", value); }
}
Now, in my service class, I tried something like:
public MyPart Create(MyPartRecord record)
{
MyPart part = Services.ContentManager.Create<MyPart>("My");
...
part.MyProperty = record.MyProperty; //getting error here
...
return part;
}
However, I am getting the following error: Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable<string>'
Essentially, I am trying to save choices from a checkboxlist (one or more selections) as a comma-separated list in the DB.
And this doesn't even get me over the problem of how do I use the enum. Any thoughts?
For some background:
I understand that the appropriate way to handle this relationship would be to create a separate table and use IList<MyEnum>. However, this is a simple list that I do not intend to manipulate with edits (in fact, no driver is used in this scenario as I handle this on the front-end with a controller and routes). I am just capturing data and redisplaying it in the Admin view for statistical/historical purposes. I may even consider getting rid of the Part (considering the following post: Bertrand's Blog Post.
It should be:
part.MyProperty = new[] {"foo", "bar"};
for example. The part's setter will store the value on the record's property as a comma-separated string, which will get persisted into the DB.
If you want to use enum values, you should use the Parse and ToString APIs that .NET provide on Enum.

How do I improve query speed on a paged grid with a large number of results?

I am querying data from our IBM i and displaying it in a grid. The purpose of displaying all records is for a couple reasons:
The existing software isn't used properly and people aren't closing out the items. (user/training issue yes, but see other items). So narrowing down the list to just open items isn't accurate.
It allows a user to query all history (this is property based and history can be important)
However, there currently is 28,000 items and will ever increase. Right now, I am using MvcContrib grid. Here is my code:
public ActionResult Index(GridSortOptions gridSortOptions, int? page, int? filterPropertyUniqueKey, int? filterPermitNumber)
{
#region Filter and Sort
var permits = buildingPermitRepository.GetOpenPermits();
// Set default sort and apply filters
if (filterPermitNumber.HasValue)
{
permits = permits.Where(w => w.PermitId == filterPermitNumber.Value);
}
// TODO add more filters
if (String.IsNullOrEmpty(gridSortOptions.Column))
{
gridSortOptions.Column = "DateApplied";
gridSortOptions.Direction = SortDirection.Descending;
}
var permitsPagedList = permits.OrderBy(gridSortOptions.Column, gridSortOptions.Direction).AsPagination(page ?? 1, 20);
#endregion
var viewModel = new PermitIndexViewModel
{
BuildingPermits = permitsPagedList,
GridSortOptions = gridSortOptions
};
return View(viewModel);
}
What would you suggest I do differently to improve the display speed? At least for subsequent views.
I don't know how AsPagination method works, but we use Skip and Take methods.
So after all filtering is done your code could look like this:
var permitsPagedList = permits.OrderBy(gridSortOptions.Column, gridSortOptions.Direction).Skip(pageSize * (page -1)).Take(pageSize).ToList();
This simple method returns only those rows which we actualy needs.

Trying to use [Description] data annotation attribute with existing code

SLIGHT UPDATE BELOW
I am trying to use the [Description] data annotation attribute with enums in order to display a friendly name. I've searched around a lot and cannot get anything implemented. Right now I have code that will display an enum as a string (using an extension), but I am not liking ThisIsAnEnum as an enum name (which is spaced out by the string extension) and it prohibits me from having longer names (which I need to maintain) such as for a radio button item. My goal is to have longer descriptions for radio button items without having to write really long enums. An extension/helper will probably be the right way to go, but I need to "fit" it into the code I am using, which is where I failed using the many examples out there.
The code I am using is generic, in that depending upon some logic either a radio button list, check box list, drop down list, select list or regular text boxes are displayed. For multi-item lists enum's are used, and the enum name is what is displayed (after using the string extension).
Here is the particular code that displays the enum:
public static IEnumerable<SelectListItem> GetItemsFromEnum<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 = name.ProperCase(),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
ProperCase is the class that changes the enum to something readable.
I found something that almost worked:
public static string GetEnumDescription<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Description;
else
return value.ToString();
}
in which case I changed code from Text = name.ProperCase(), to Text = name.GetEnumDescription(...) but if I put value in the parenthesis I get a "does not exist in the current context" message (which I tried fixing but just made the problem worse). If I leave it blank I get the "No overload for ... takes 0 arguments" (again, understandable - but I don't know how to fix). And if I put name in the parenthesis the code compiles but upon viewing the page I get the "Object reference not set..." error on this line:
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes
(typeof(DescriptionAttribute), false);
I've spent a lot of time on this and know that my stumbling block is the
Text = name.ProperCase(),
code. Any ideas/help? Thanks in advance.
UPDATE:
If I do:
Text = GetEnumDescription(selectedValue),
I actually DO get the [Description] text, however, it just displays for the first enum. So, if I have 5 enums all with different [Description]'s the code just repeats the [Description] for the first enum 5 times instead of displaying differently for each. I hope that makes sense and gets to narrow down the problem.
I'd recommend you the Display attribute:
public static IEnumerable<SelectListItem> GetItemsFromEnum<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 = name == selectedValue.ToString()
};
}
public static string GetEnumDescription(string value, Type enumType)
{
var fi = enumType.GetField(value.ToString());
var display = fi
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (display != null)
{
return display.Name;
}
return value;
}
and then you could have:
public enum Foo
{
[Display(Name = "value 1")]
Value1,
Value2,
[Display(Name = "value 3")]
Value3
}
And now you could have:
var foo = Foo.Value2;
var values = GetItemsFromEnum(foo);
Also notice that I have modified the Selected clause in the LINQ expression as yours is not correct.
This being said, personally I would recommend you staying away from enums on your view models as they don't play nicely with what's built-in ASP.NET MVC and you will have to reinvent most of the things.

Using DataObjectTypeName in DataObjectSource

The functionality I am trying to use is:
- Create a ObjectDataSource for selection and updating controls on a web page (User Control).
- Use the DataObjectTypeName to have an object created that would send the data to an UpdateMethod.
- Before the values are populated in the DataObjectTypeName’s object, I would like to pre-populate the object so the unused items in the class are not defaulted to zeros and empty strings without me knowing whether the zero or default string was set by the user or by the application.
I cannot find a way to pre-populate the values (this was an issue back in 2006 with framework 2.0). One might ask “Why would anyone need to pre-populate the object?”. The simple answer is: I want to be able to randomly place controls on different User Controls and not have to be concerned with which UpdateMethod needs to handle which fields of an object.
For Example, let’s say I have a class (that reflects a SQL Table) that includes the fields: FirstName, LastName, Address, City, State, Zip. I may want to give the user the option to change the FirstName and LastName and not even see the Address, City, State, Zip (or vice-versa). I do not want to create two UpdateMethods where one handled FirstName and LastName and the other method handles the other fields. I am working with a Class of some 40+ columns from multiple tables and I may want some fields on one screen and not another and decide later to change those fields from one screen to another (which breaks my UpdateMethods without me knowing).
I hope I explained my issue well enough.
Thanks
This is hardly a solution to the problem, but it's my best stab at it.
I have a GridView with its DataSourceID set to an ObjectDataSource.
Whenever a row is updated, I want the property values in the object to be selectively updated - that is - only updated if they appear as columns in the GridView.
I've created the following extension:
public static class GridViewExtensions
{
public static void EnableLimitUpdateToGridViewColumns(this GridView gridView)
{
_gridView = gridView;
if (_gridView.DataSourceObject != null)
{
((ObjectDataSource)_gridView.DataSourceObject)
.Updating += new ObjectDataSourceMethodEventHandler(objectDataSource_Updating);
}
}
private static GridView _gridView;
private static void objectDataSource_Updating(object sender, ObjectDataSourceMethodEventArgs e)
{
var newObject = ((object)e.InputParameters[0]);
var oldObjects = ((ObjectDataSource)_gridView.DataSourceObject).Select().Cast<object>();
Type type = oldObjects.First().GetType();
object oldObject = null;
foreach (var obj in oldObjects)
{
if (type.GetProperty(_gridView.DataKeyNames.First()).GetValue(obj, null).ToString() ==
type.GetProperty(_gridView.DataKeyNames.First()).GetValue(newObject, null).ToString())
{
oldObject = obj;
break;
}
}
if (oldObject == null) return;
var dynamicColumns = _gridView.Columns.OfType<DynamicField>();
foreach (var property in type.GetProperties())
{
if (dynamicColumns.Where(c => c.DataField == property.Name).Count() == 0)
{
property.SetValue(newObject, property.GetValue(oldObject, null), null);
}
}
}
}
And in the Page_Init event of my page, I apply it to the GridView, like so:
protected void Page_Init()
{
GridView1.EnableLimitUpdateToGridViewColumns();
}
This is working well for me at the moment.
You could probably apply similar logic to other controls, e.g. ListView or DetailsView.
I'm currently scratching my head to think of a way this can be done in a rendering-agnostic manner - i.e. without having to know about the rendering control being used.
I hope this ends up as a normal feature of the GridView or ObjectDataSource control rather than having to hack it.

Resources