.NET MVC Updating the View with a model property - c#

I used the approach described in this article to create a drop down.
The Model
public class IceCreamFlavor
{
public int Id { get; set; }
public string Name { get; set; }
}
The View Model
public class ViewModel
{
private readonly List<IceCreamFlavor> _flavors;
[Display(Name = "Favorite Flavor")]
public int SelectedFlavorId { get; set; }
public IEnumerable<SelectListItem> FlavorItems
{
get { return new SelectList(_flavors, "Id", "Name");}
}
}
The View
#Html.LabelFor(m=>m.SelectedFlavorId)
#Html.DropDownListFor(m => m.SelectedFlavorId, Model.FlavorItems)
#Html.ValidationMessageFor(m=>m.SelectedFlavorId)
<input type="submit" value="Submit" />
This approach works fine.
Now I want to display a property of the Model on the same view. As an example assume we had the following properties.
public class IceCreamFlavor
{
public int Id { get; set; }
public string Name { get; set; }
public float Price { get; set; }
}
Now underneath the Dropdown I need to display the price as
Price : 15.99
How can I achieve this?

I would rather choose a another solution, since firing ajax for every selected input is useless and consuming.
Using the normal current DropDownListFor in addition with outputting the complete price list to hiding input value. value e.g: '10.0;12.0;...' which every value is than can be taken by simple JavaScript procedure with the option index as the mark for the value you should take.
Constructing a new MyDropDownListFor which will follow as the current but instead of just constructing normal <option>.. it will also add to that html tags the price or whatever additional parameter you want it to display as well. Examples: Here Here Here
No matter what solution you take, it will have to be combined with supporting simple JavaScript method which then renders the Selection and Displaying the Price which already been downloaded.

To render a property off of the model after submitting, you can just break into HTML to display it:
#if (Model.Price != 0.0F)
{
<b>Price #Model.Price.ToString("0.00") </b>
}
To achieve this, add a collection onto the ViewModel:
public class ViewModel
{
private readonly System.Collections.Generic.List<IceCreamFlavor> _flavors;
public ViewModel()
{
// Construct Flavors
}
public List<IceCreamFlavor> AllFlavors
{
get
{
return _flavors;
}
}
[Display(Name = "Favorite Flavor")]
public int SelectedFlavorId { get; set; }
public System.Web.Mvc.SelectList FlavorItems
{
get { return new System.Web.Mvc.SelectList(_flavors, "Id", "Name");}
}
}
Then on the View:
#if (Model.AllFlavors.Any(f => f.Id == Model.SelectedFlavorId))
{
<b>Price #Model.AllFlavors.First(f => f.Id == Model.SelectedFlavorId).Price.ToString("0.00") </b>
}
You could, of course, just expose the selected Flavor as a property on the ViewModel (similar display principle applies). But, the advantage of exposing all the Flavors as a property, is you can easily move to storing this in JavaScript on page and query that, rather than relying on the submit button.
Then you can roll your own drop down onchange events using JavaScript / JQuery to read from this object stored on page. (Or use AJAX to make a call to another action to return the value as needed..)
The solution not exposing all flavors is:
Property on ViewModel:
public IceCreamFlavor SelectedFlavor
{
get
{
return _flavors.FirstOrDefault(f => f.Id == this.SelectedFlavorId);
}
}
Display on View:
#if (Model.SelectedFlavor != null)
{
<b>Price #Model.SelectedFlavor.Price.ToString("0.00") </b>
}

Related

Creating a Reusable Enum to Checkbox List using Razor Pages Partial, TagHelper or ViewComponent

I am attempting to create a neat, reusable checkbox list for enums with the [flags] attribute in Razor Pages with .NET Core 7.
I do not know which worflow to use - Partial, TagHelpers, ViewComponent or any others (or a combination), neither how I might apply these tools (having no experience creating any of them) to create a clean, efficient and reusable tool/helper.
The code below works, but it is not particularly reusable - for example, if I wanted to change the html so the label element became a parent of the checkbox input, I will need to change this in every instance of the 'cut and pasted' cshtml code.
In addition, the call to the helper function MyHtmlHelpers.EnumToCheckboxList<Urgency>(nameof(TransportReferral), nameof(TransportReferral.Urgency), TransportReferral?.Urgency) seems verbose and inefficient when compared to TagHelpers. Instead, it would be ideal to be able to access all these arguments with a single reference - in a similar way the TagHelpers do with the asp-for attribute, but I do not know how this might be achieved.
public static partial class MyHtmlHelpers
{
public static IEnumerable<CheckboxListItem> EnumToCheckboxList<TEnum>(string? modelName, string propertyName, TEnum? enumValue) where TEnum : struct, Enum
{
string name = string.IsNullOrEmpty(modelName)
? propertyName
: modelName + '.' + propertyName;
string idPrefix = name.Replace('.', '_');
return Enum.GetValues<TEnum>().Select(e =>
{
var eStr = e.ToString();
var eInt = Convert.ToInt32(e).ToString();
// ignoring DisplayAttribute.Name
return new CheckboxListItem
{
Display = typeof(TEnum).GetMember(eStr)[0]
.GetCustomAttributes<DescriptionAttribute>(false)
.FirstOrDefault()?
.Description ?? SplitCamelCase(eStr),
IsChecked = enumValue.HasValue && enumValue.Value.HasFlag(e),
Value = eInt,
Name = name,
Id = idPrefix + '_' + eInt,
};
}).ToList();
}
public static string SplitCamelCase(string input)
{
return lowerUpper().Replace(input, "$1 $2");
}
[GeneratedRegex("([a-z])([A-Z])", RegexOptions.CultureInvariant)]
private static partial Regex lowerUpper();
}
public class CheckboxListItem
{
public string Display { get; set; }
public string Value { get; set; }
public string Name { get; set; }
public string Id { get; set; }
public bool IsChecked { get; set; }
}
consumed in a cshtml page like so:
#foreach (var e in MyHtmlHelpers.EnumToCheckboxList<Urgency>(nameof(TransportReferral), nameof(TransportReferral.Urgency), Model.TransportReferral?.Urgency))
{
<div class="form-check form-check-inline">
<input type="checkbox"
name="#e.Name"
id="#e.Id"
checked="#e.IsChecked"
value="#e.Value">
<label class="form-check-label" for="#e.Id">
#e.Display
</label>
</div>
}
So in summary, is there a way to refactor the above code, taking advantage of Razor pages tools, to make the cshtml markup more reusable and also allow the full name TransportReferral.Urgency and its value to be passed cleanly to the tool with a single argument, similarly to (or in the same way) the asp-for attribute does for taghelpers?
Essentially what you want to do is replicate the SelectTagHelper with enum support, except you want to render checkboxes instead of option elements. Given that, I would start with the source code for the SelectTagHelper
https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.TagHelpers/src/SelectTagHelper.cs
and the GetEnumSelectList helper https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.ViewFeatures/src/HtmlHelper.cs#L398

How can I add different controls dynamically to a web page using asp.net core?

I am new to .net core - have been using aspx web pages and .net framework 4.x for a number of years. I have a project where we want to display different controls (textbox, dropdown, checkbox) on the page based on values returned from a query. For example, user chooses "A" from a dropdown list and it shows 10 controls, if they choose object B it shows 8 controls, etc. Previously in .net framework, I would use a content placeholder with an ID and then find that ID and start adding controls (controls.Add(newControl)) in the placeholder. It doesn't seem that is an option with .net core. It seems like this would be a common need for various web applications, but I'm not finding many hits.
Another question is whether this can be done in the code behind or if it has to be done on the client-side. If one of the controls in the list is a dropdown, there will be a query that a subroutine will run to get the Key/Value pairs for the dropdown. To me this means it would be more effective on the server side.
I haven't really found any good examples when I do some searching. Can anyone point me to a good resource or provide me with a basic example - either client-side or server-side? Thanks!
There are many options, but I'll describe a simple one, using server side processing. As you explained in your comment, there will be 2 pages:
One that will display the select element that will be used to choose a set of controls.
The page that will be returned according to the previous choise, displaying the selected set of controls.
I assume that you know how to build the first page.
For the second page, you can leverage the ASP.NET Core MVC pattern to achieve the desired result.
You will need the three usual MVC elements:
An Action in a Controler.
A ViewModel for your Razor View.
A Razor View.
The Action does the following:
Receives the id of the selected set of control (via the Action's parameter).
Uses this id to retrieve the information about the corresponding set of controls from your repository.
Builds a ViewModel out of the received information.
Builds a View using the obtained ViewModel.
Return the builded View.
Here is some simplified example code:
In your controller, add the following method:
#!lang-cs
Public IActionResult GetProgramControlSet(int ProgramId)
{
// Here, use the id to get the data from your repository
// that will be used to build set of controls.
// Supposing you have defined a GetControls method,
// it could look like:
var SelectedControls = MyRepository.GetControls(ProgramId);
// If needed, you can build a ViewModel out of the received SelectedControls.
var SelectedControlsViewModel = new ControlSetViewModel(SelectedControls);
return View(SelectedControlsViewModel)
}
Of course, many things are missing here: error handling, etc...
Here is what the ViewModel could be:
#!lang-cs
public class ControlSetViewModel
{
public string Name { get; private set; }
public List<IControl> Controls { get; private set; }
public ControlSetViewModel(...)
{
// Whatever needs to be done to construct the ViewModel
}
}
public enum ControlKind
{
Button,
Select,
Textarea
//...
}
public interface IControl
{
ControlKind Kind { get; }
}
public class ControlButton : IControl
{
public ControlKind Kind => ControlKind.Button;
public string Label { get; set; }
public string Text { get; set; }
public string Color { get; set; }
// ... All other needed properties for the button
}
public class ControlTextarea : IControl
{
public ControlKind Kind => ControlKind.Textarea;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public string RowCount { get; set; }
// ... All other needed properties for the textarea
}
public class ControlSelect : IControl
{
public ControlKind Kind => ControlKind.Select;
public string Label { get; set; }
public string PlaceholderText { get; set; }
public List<SelectOption> Options { get; set; }
// ... All other needed properties for the select
}
public class SelectOption
{
public string Text { get; set; }
public string Value { get; set; }
}
You could also use inheritance instead of interface for the control classes.
Now the view.
It is a Razor page containing something akin to
#model ControlSetViewModel
#*... some HTML ...*#
<div>
<h1>#Model.Name</h1>
#foreach(var control in Model.Controls)
{
<div>
switch(control.GetControlKind())
{
case ControlKind.TextArea:
var Textarea = (ControlTextarea)control;
<label>#Textarea.Label</label>
<textarea rows="#Textarea.RowCount"/>
break;
case ControlKind.Select:
var Select = (ControlSelect)control;
<label>#Select.Label</label>
<select>
#foreach(var option in Select.Options)
{
<option value="#option.Value">#option.Text</option>
}
</select>
break;
#*... etc ...*#
default:
#*... etc ...*#
}
</div>
}
</div>
#*... More HTML ...*#
Of course this is far to be finished. All the infrastructure and code that will actually react to the displayed controls is missing.
Is it a form you that will be posted?
Is it Javascript code that will react to the control manipulation?
Or another mecanism?
This questions will need to be addressed.

An object used as a model in a partial view creates another in the controller?

There is something weird going on in my app. It's not dammageable, but it's a curious behavior and I'm reaching out to you to understand what's happening.
I was working on some partial view based on a model, and it worked. I figured out I had to replace a lot of stuff with the correct input.
So here's a snippet of my old model:
public class SearchObjInfo
{
public string m_ObjName { get; set; }
public string m_ObjType { get; set; }
public decimal? m_ObjNumber { get; set; }
public string m_ObjSymbol { get; set; }
public string m_ObjPower { get; set; }
}
And here's the same snippet with the new class I made to construct this partial view:
public class SearchObjInfoPartial
{
public string m_ObjName { get; set; }
public IEnumerable<SelectListItem> m_ObjType { get; set; }
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:0}")]
public int m_ObjNumber { get; set; }
public IEnumerable<SelectListItem> m_ObjPower { get; set; }
public IEnumerable<SelectListItem> m_ObjSymbol { get; set; }
}
Now the way the render is made is actually quite identical, and not. I used lots of stuff like these before:
<label>
Text: Write a name, part of a name, or a word.
</label>
Object Name: #Html.TextBox("_objectName") <br/>
Object Number: <input type="number" min="0" max="9999" name="_objNumber" value="decimal" style="width: 70px"/><br/>
Type: #Html.DropDownList("_objType", "All") <br/>
Power: #Html.DropDownList("_objSymbol", "=") #Html.DropDownList("_objValue", String.Empty)<br/>
But now I render my partial view this way:
#model MyApp.Utilities.SearchObjInfoPartial
Object Name: #Html.TextBoxFor(item => item.m_ObjName, Model.m_ObjName, String.Empty) <br/>
Object Number: #Html.EditorFor(item => item.m_ObjNumber)<br />
Power: #Html.DropDownListFor(item => item.m_ObjPower, Model.m_ObjPower, String.Empty) #Html.DropDownListFor(item => item.m_ObjSymbol, Model.m_ObjSymbol, String.Empty)
Type: #Html.DropDownListFor(item => item.m_ObjType, Model.m_ObjType, String.Empty) <br/>
Before rendering I deal with the SelectLists, no problems here.
Now here's where it gets interesting:
In my controllers I used to have methods receiving huge amounts of data (see here: How to deal with many possible values to make a query?)
But now I made something else. Without thinking, I tried to add the old search model in the controller method like this:
public ActionResult BrowseObjectList(SearchObjInfo searchObj, string _objName, (...))
And I just found out that it works even if the receiving object is not the same as the one used in my partial view model. How is that even possible? I mean, the proper fields will fill up and I can "safely" deal with my searchObj item, though I do not find this secure after all...
Thats what MVC framework does for you man.
Browser simply sends the form collection to server as Name Value Collection. As the request hits server, MVC framework will match the values with parameter in the Action method.
Form collection values are mapped to Model object properties. This is done by doing a match with Property Name and Name of the value in Form collection. Just check the client side code by view source, you can see that the input tags will have an attribute 'name' which matches with the property name of model.
QueryString values will also be mapped to parameters in Action method based on name.
Even you add a hidden field and specify a parameter with same name in action method...tada you will get the value of hidden field in that variable on post back
In your case though the model is different, its property name are same m_ObjName, m_ObjType, m_ObjNumber, m_ObjSymbol, m_ObjPower. So MVC do a match for you.
Try with different property name and see the results ;-)

using ViewBag to pass a model object

I'm trying to sort my result page (which is in another view than the filtration page). I have faced this weird issue I do not understand why keeps happening to me.
All the codes provided in very short form, please ask me if you need any other parts of my code for more information.
My Index view(where user filters results):
#model IEnumerable<Cars.Models.CarSearch>
#using (Html.BeginForm("SearchResult", "Home", FormMethod.Post,
new
{
id = "CategoryFormID",
data_modelListAction = #Url.Action("ModelList"),
data_makeListAction = #Url.Action("MakeList"),
data_editionListAction = #Url.Action("EditionList")
}))
{
<label>Make</label>
<select id="MakeID" name="carMake">
<option>All Makes</option>
</select>
}
My SearchResult view:
#model IEnumerable<Cars.Models.Car>
Make
My model:
public class Car
{
public String Make { get; set; } //is my table model
}
public class CarFilter {
public String carMake { get; set; }
}
public class CarSearch {
public CarFilter CarFilter { get; set; }
public byte PageSize { get; set; }
public short PageNumber { get; set; }
public int TotalRows { get; set; }
}
My Controller:
public ActionResult SearchResult(String sortOrder, CarFilter filters)
{
ViewBag.CurrentFilters = filters;
return View();
}
All I'm trying to do is to get carMake from Index post it to controller in CarFilter form (since in my code there are LOTS of fields in the form and I don't want to write them all down) and when user clicks on sort by Make it GET the SearchResult method and it's supposed to set filters = ViewBag.CurrentFilters which is the value user inputted from beginning.
Now the funny part is, when I replace CarFilter filters with String carMake and other places respectively. It works like a charm.
My question:
Why?
How can I do this with CarFilter filters?
UPDATE:
Problem is that filters = ViewBag.CurrentFilters in my SearchResult view does not work with the type CarFilter, because it keeps giving me NULL value when user clicked on the sort by Make.
Second UPDATE:
I tried changing filters = ViewBag.CurrentFilters with CarFilter = ViewBag.CurrentFilters. Now CarFilter filters in my SearchResult(...)method in my controller is not and null object, but ALL the values of the objects in the model class is null (which shouldn't be). I mean the filters object exists but it seems like the values of CarFilter class in my model haven't been passed by ViewBag.CurrentFilters to the view.
when you canged the name it worked because framework found property name and the bind it to what you have within action parameters doesnt work so nicely with objects. My advice is to stick with simple types
Here is similiar case:
How to send model object in Html.RenderAction (MVC3)
Its not a ViewBag problem thants how it works in general. Its the prime reason for using flatted models :/

ASP.NET MVC Forms for Model

I'm trying to learn MVC by building a full-featured website. I'm a little stuck when it comes to dealing with forms, and posting data, and models....
BTW: I'm using EF Code-First w/MS SQL CE
Here's the Models in question:
public class Assignment
{
public int AssignmentID { get; set; }
public int? CourseID { get; set; }
public string Name { get; set; }
// etc...
public virtual Course Course { get; set; }
}
public class Course
{
public int CourseID { get; set; }
// etc...
}
I'm loading a partial view that allows the user to add a new assignment
Controller:
public ActionResult Assignments()
{
var assignments = myContext.Assignments.OrderBy(x => x.DueDate);
return View(assignments);
}
[HttpPost]
public ActionResult AddAssignment(Assignment assignment)
{
myContext.Assignments.Add(assignment);
myContext.SaveChanges();
return RedirectToAction("Assignments");
}
// Returns a strongly-typed, partial view (type is Assignment)
public ActionResult AddAssignmentForm()
{
return PartialView();
}
Here's where I'm stuck: I want this form to have a drop down list for the different courses that an assignment could possibly belong to. For example, an assignment called "Chapter 3 Review, Questions 1-77" could belong to course "Pre-Algebra". However, if I use the code below, I have to explicitly declare the SelectListItems. I thought that with the given Assignment model above, I should be able to have the drop down list for Courses automatically generated using MVC awesomeness. What am I doing wrong?
AddAssignment Partial View:
#model MyModels.Assignment
#using(Html.BeginForm("AddAssignment", "Assignments"))
{
// Can't I create a drop down list without explicitly
// setting all of the SelectListItems?
#Html.DropDownListFor(x => x.Course, ....
}
Basically you are confusing/mixing your business model and your UI model.
The quick fix here is to add the data for the dropdown list to the ViewBag (a dynamic object).
Alternatively you could create a class AssignmentModel that contains the relevant Assignment properties and the List.
And No, this is not well supported in the templates.
You do realize you'll need some error handling in the Post method(s)?

Categories

Resources