I have this text box defintion in my view
#Html.TextBoxFor(model => model.MaxNumberOfExtensions == "0" ? "Unlimited" : model.MaxNumberOfExtensions, new { required = "required", id = "maxNumberOfExtensions" })
What I am trying to do is say,
If the value MaxNumberOfExtensions is 0 then display 'Unlimited' in
the text box, otherwise display, the value in the field
MaxNumberOfExtensions
This does not work at runtime. It gives the error
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
What is the correct way of doing this?
Such complex lambda expressions are not supported by the HTML helpers. They only support property access and indexer access expressions.
The correct way is to use a view model of course. So you would have a property which will already contain the required text based on the value of your domain model.
For example:
public class MyViewModel
{
public string MaxNumberOfExtensions { get; set; }
}
and then in your controller action rendering this view:
public ActionResult SomeAction()
{
SomeDomainModel model = ...
MyViewModel viewModel = new MyViewModel();
viewModel.MaxNumberOfExtensions = model.MaxNumberOfExtensions == "0" ? "Unlimited" : model.MaxNumberOfExtensions;
return View(viewModel);
}
and then in the view:
#model MyViewModel
...
#Html.TextBoxFor(x => x.MaxNumberOfExtensions, new { required = "required", id = "maxNumberOfExtensions" })
Alternatively if your application is currently not following best practices (not using view models) you could write a custom template where you could perform this task.
For example, add the following template in ~/Views/Shared/EditorTemplates/FormattedNumberOfExtensions.cshtml:
#model string
#{
var value = Model == "0" ? "Unlimited" : Model;
}
#Html.TextBox("", value, new { required = "required", id = "maxNumberOfExtensions" })
and then in your view:
#Html.EditorFor(x => x.MaxNumberOfExtensions, "FormattedNumberOfExtensions")
Related
I need to set data into a textarea in asp.net mvc razor page (.cshtml) from controller in C#. My code snippet as following:
I am not able to assign data into model.
In cshtml:
#Html.TextBoxFor(b => b.query, new { Value = ViewBag.query, height = "500px;" })
In controller:
ViewBag.query = "Hello";
model.query = ViewBag.query;
In Model:
[Display(Name = "Query")]
public string query { get; set; }
I don't see any reason why you should use the Value parameter when the value for the field has been assigned in the controller. I am not sure why this is not working because you didn't provide the whole code so i'll just try to fill in the blanks.
Model:
public class ModelName
{
[Display(Name = "Query")]
public string query { get; set; }
}
In the controller, make sure you return the model.
public class ControllerName : Controller
{
public ActionResult ActionName()
{
ViewBag.query = "Hello";
var model = new ModelName
{
query = ViewBag.query
}
return View(model)
}
}
In the view, make sure the object type is referenced on the page...
#model ApplicationName.Models.ModelName
So if anywhere in the view, you can get the value for the text area this way.
#Html.TextAreaFor(model => model.query, new { #class = "form-control", placeholder = "Add Placeholder", id = "textarea", autocomplete = "off", #rows = "10", style = "width: 70%; max-width: 100%;" }
You can always change the style or don't use any at all depending on what you want.
You can define multiline textbox in following way :
#Html.TextAreaFor(x => x.Body, 10, 15, null)
I have a very basic #Html.TextAreaFor() on a form, and for some reason, the text area's value never makes it to my controller.
VIEW
<p class="SmallText">(Multiple Entries Allowed)</p>
#Html.TextAreaFor(x => x.quickSearch, new { cols = 30, #rows = 5 })
VIEW-MODEL
public String quickSearch;
CONTROLLER
public ActionResult FindRecord(FindRecordViewModel Model)
{
var ImNotCrazy = Model.quickSearch;
}
The problem is that when I debug Model.quickSearch is always null and never has a value.
Your view model has to have Properties, not fields, to work properly with model binding. So change public String quickSearch; to public String quickSearch { get; set; }.
Also, you should use standard naming conventions and change the name of the field to QuickSearch
I'm building an app with ASP.NET MVC 4. I'm binding my model to a view. In my view, I need a drop down list. That drop down list needs to show quarters. The quarters should be displayed as "Q1", "Q2", "Q3", and "Q4". My model, only has quarter numbers. They are defined like this:
public List<short> Quarters = new List<short>() { get; set; }
public short? SelectedQuarter = null;
public void Initialize() {
Quarters.Add(1);
Quarters.Add(2);
Quarters.Add(3);
Quarters.Add(4);
}
Somehow, I need to prepend "Q" to each value. However, I'm not sure how to do this in ASP.NET MVC. How does someone do this?
Thanks!
Create a SelectList to be used by DropdownListFor() so that you bind the selected option to property SelectedQuarter, but display the 'friendly' name.
View model
public class MyViewModel
{
[Display(Name = "Quarter")]
[Required]
public short? SelectedQuarter { get; set; } // must be a property, not a field!
IEnumerable<SelectListItem> QuarterList { get; set; }
}
Controller
public ActionResult Edit()
{
MyViewModel model = new MyViewModel();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Edit(MyViewModel model)
{
if(!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// model.SelectedQuarter contains the selected value
}
private void ConfigureViewModel(model)
{
model.SelectedQuarter = new List<SelectListItem>()
{
new SelectListItem() { Value = "1", Text = "Q1" },
new SelectListItem() { Value = "2", Text = "Q2" },
new SelectListItem() { Value = "3", Text = "Q3" },
new SelectListItem() { Value = "4", Text = "Q4" },
}
}
View
#model MyViewModel
#using(Html.BeginForm())
{
#Html.LabelFor(m => m.SelectedQuarter)
#Html.DropDownListFor(m => m.SelectedQuarter, Model.QuarterList, "-Please select-")
#Html.ValidationMessageFor(m => m.SelectedQuarter)
<input type="submit" />
}
Assuming you have this property:
public List<short> Quarters { get; set; }
Then in your view or any other consuming code you can generate a list of strings with something like:
model.Quarters.Select(q => "Q" + q)
or:
model.Quarters.Select(q => string.Format("Q{0}", q))
However, semantically it really feels like this belongs on a view model and not in consuming code. Ideally the view should only ever need to bind directly to properties on the view model, not transform those properties. Something like this:
public IEnumerable<string> QuartersDisplay
{
get { return Quarters.Select(q => string.Format("Q{0}", q)); }
}
Then consuming code can just bind to that property:
model.QuartersDisplay
(If the model is a domain model then I'd recommend introducing a view model between the domain and the view, since this wouldn't belong on a domain model.)
Thinking about this a little more... Do you want one property with both the displays and the backing values for the drop down list? That would likely be a IDictionary<short, string>, I imagine? Something like this:
public IDictionary<short, string> QuartersOptions
{
get { return Quarters.ToDictionary(q => q, q => string.Format("Q{0}", q)); }
}
In which case you'd bind to that property:
model.QuartersOptions
Keep in mind that a drop down list often binds to two things. The property which holds the list of possible values (which is what we've built here) and the property which holds the selected value (which remains your SelectedQuarter property).
View:
#Html.DropDownList("CategoryItems", null, new { #class = "ddlcs" })
#Html.ValidationMessage("CategoryItems")
Controller:
var cat = from s in db.CategoryDbSet
where s.IsActive == true
orderby s.CatName
select new { s.CatID, s.CatName };
var catListItems = cat.ToList()
.Select(c => new SelectListItem
{
Text = c.CatName,
Value = c.CatID.ToString()
})
.ToList();
catListItems.Insert(0, new SelectListItem
{
Text = "[--Select the category--]",
Value = ""
});
ViewBag.CategoryItems = catListItems;
I wish to enforce the required validation on the dropdown when someone selects the "Select the category" option during the save action. I am new to MVC framework and i am not sure where am i making the mistake ? This dropdown is not tied up with the Model.
Please suggest the soln.
This dropdown is not tied up with the Model.
That's the mistake. Validation in ASP.NET MVC works by decorating your view model properties with the respective attributes. For example if you want to make this dropdown required, you would decorate the corresponding property on your view model with the [Required] attribute.
So add the necessary properties to your existing view model:
public class MyViewModel
{
[Required]
public int? SelectedCategoryId { get; set; }
public IEnumerable<SelectListItem> Categories { get; set; }
... some other properties that your view might need
}
and then in your controller action populate this view model:
var model = new MyViewModel();
model.Categories = cat
.ToList()
.Select(c => new SelectListItem
{
Text = c.CatName,
Value = c.CatID.ToString()
}).ToList();
return View(model);
and in your view use the strongly typed versions of the helpers:
#Html.DropDownListFor(
x => x.SelectedCategoryId,
Model.Categories,
"[--Select the category--]",
new { #class = "ddlcs" }
)
#Html.ValidationMessageFor(x => x.SelectedCategoryId)
If you want only client side validation, you may do this:
$('form').validate({
rules:{
CategoryItems: 'required'
}
});
Working demo.
But I wouldn't suggest doing so as the client side validation is for better user experience and can be easily bypassed. The correct way to do this is described in Darin's answer, using dataannotations and view models.
I have read somewhat on the post-redirect-get design pattern and I'm not sure if it works for my purpose as what I have is an MVC site which is design to look like an application, I have multiple dropdowns on the page which all bind to an integer array as below in my controller:
[HttpPost]
public ViewResult ResponseForm(PartyInvites.Models.GuestResponse response, int[] SelectedCustomer)
{
return View(response); // works but resets all my selected dropdowns
// return View(); // gives an error that it can't rebind items in view
}
My View:
#foreach (Schedule sched in Model.Schedules)
{
#Html.DropDownList("MySelectedCustomer", new SelectList(sched.Customers, "Id", "FirstName"), "Select A Customer", new { #class = "SelectedCustomer" })
}
The GuestResponse:
public class GuestResponse
{
[Required(ErrorMessage = "You must enter your name")]
public string Name { get; set; }
public string SomeString = "someString";
public string Email { get; set; }
public string Phone { get; set; }
public bool? WillAttend { get; set; }
public int SelectedSchedule = 0;
public int SelectedCustomer = 0;
public List<Schedule> Schedules
{
get
{
return new List<Schedule>() { new Schedule() { ScheduleName = "party1", ScheduleId = 1 }, new Schedule() { ScheduleId = 2, ScheduleName = "party2" } };
}
set
{
Schedules = value;
}
}
}
The SelectCustomer property is a property on the GuestResponse class. All the dropdowns are bound and if I change a few they bind nicely to the int[] SelectedCustomer collection. However I want to return my View back (so it does nothing essentially) but this resets all the dropdowns to their original state as the response was never fully bound because there was multiple dropdowns and MVC couldn't model bind to it. What it the best way of doing this so it maintains state so to speak?
The correct way to handle this is to use a view model instead of passing your domain models to the view.
But if you don't want to follow good practices you could generate your dropdowns like this as a workaround:
for (int i = 0; i < Model.Schedules.Count; i++)
{
#Html.DropDownList(
"MySelectedCustomer[" + i + "]",
new SelectList(
Model.Schedules[i].Customers,
"Id",
"FirstName",
Request["MySelectedCustomer[" + i + "]"]
),
"Select A Customer",
new { #class = "SelectedCustomer" }
)
}
The correct way is to have a property of type int[] SelectedCustomers on your view model and use the strongly typed version of the DropDownListFor helper:
for (int i = 0; i < Model.Schedules.Count; i++)
{
#Html.DropDownListFor(
x => x.SelectedCustomers,
Model.Schedules[i].AvailableCustomers,
"Select A Customer",
new { #class = "SelectedCustomer" }
)
}
and your POST controller action will obviously take the view model you defined as parameter:
[HttpPost]
public ViewResult ResponseForm(GuestResponseViewModel model)
{
// The model.SelectedCustomers collection will contain the ids of the selected
// customers in the dropdowns
return View(model);
}
And since you mentioned the Redirect-After-Post design pattern, this is indeed the correct pattern to be used. In case of success you should redirect to a GET action:
[HttpPost]
public ViewResult ResponseForm(GuestResponseViewModel model)
{
if (!ModelState.IsValid)
{
// the model is invalid => redisplay the view so that the user can fix
// the errors
return View(model);
}
// at this stage the model is valid => you could update your database with the selected
// values and redirect to some other controller action which in turn will fetch the values
// from the database and correctly rebind the model
GuestResponse domainModel = Mapper.Map<GuestResponseViewModel, GuestResponse>(model);
repository.Update(domainModel);
return RedirectToAction("Index");
}
Note: I'm first addressing why it's not binding anything, but that's not addressing the array issue, which I will get to afterwards. Where most people go wrong with MVC is that they do not take advantage of the built-in features of MVC to deal with these situations. They insist on doing foreach's and manually rendering things, but do not take into account the collection status.
The reason why the values are reset is because you are using Html.DropDownList() rather than Html.DropDownListFor(), and you are renaming the posted property name to a different name than your model property name.
You could simply change it to this:
#Html.DropDownList("SelectedCustomer", // note the removal of "My"
new SelectList(sched.Customers, "Id", "FirstName"),
"Select A Customer", new { #class = "SelectedCustomer" })
However, you would not have had this issue, and saved yourself a huge headache if you had just used the strongly typed version.
#Html.DropDownListFor(x => x.SelectedCustomer,
new SelectList(sched.Customers, "Id", "FirstName"),
"Select A Customer", new { #class = "SelectedCustomer" })
As for the Array, you should use an EditorTemplate for Schedules, and in that EditorTemplate you simply create your html as if it were a single item. That's the great thing about Editor/DisplayTemplates is that they automatically deal with collections.
Create a folder in your Views/Controller folder called EditorTemplates. In that folder, create an empty file called Schedule.cshtml (assuming Schedules is a List or array of Schedule). In that, you have code to render a single schedule.
EDIT:
Darin brings up a good point. I would make a small change to the model and add a Selected property to both Schedule and GuestResponse, then you can use Linq to return the selected schedule and it would simplify things.
EDIT2:
You some conflicts between the problem you've described and the code you've shown. I suggest you figure out exactly what you're trying to do, since your code does not really reflect a viable model for this.