Following Post method in my ASP.NET MVC Core 1.1 app adds a record to database. The method successfully adds the record as long as user selects exactly 3 order types from a multi-select dropdown. User is supposed to select at most 3 order types from the dropdown. So, if a user selects less than 3 order types it, as expected, throws the well-know error: Index was outside the bounds of the array. Question: How can I avoid the above error if user selects less than 3 order types. I guess I can place the entire var oOrder = new Order{...} statement below inside each block of an if...else.. to avoid the error. But in real scenario there are lots more model properties and hence repeating them 3 times in if...else... blocks would make the code look a more complicated than it really is. Are there any better ways of doing it?
[HttpPost]
public IActionResult AddOrder(OrderViewModel model)
{
if (ModelState.IsValid)
{
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID = model.SelectedTypeIDs[0],
lkupType_2_ID = model.SelectedTypeIDs[1],
lkupType_3_ID = model.SelectedTypeIDs[2],
....
};
_context.Add(oOrder);
}
return RedirectToAction(....);
}
UPDATE:
snapshot of the View
....
<div>....</div>
....
<div class="form-group">
<label asp-for="SelectedOrderTypeIDs"></label>
<div class="col-md-10">
<select asp-for="SelectedOrderTypeIDs" asp-items="Model.lstOrderTypes"></select>
</div>
</div>
<button type="submit" name="submit">Add Order</button>
NOTE: I'm using ASP.NET MVC tag helpers and often use this post from #Shyju for good example of multi-select tag helper.
You can try below, an if as a tenary operator:
[HttpPost]
public IActionResult AddOrder(OrderViewModel model)
{
if (ModelState.IsValid)
{
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID = (model.SelectedTypeIDs.Length > 0) ? model.SelectedTypeIDs[0] : 0, // You can default it to null if it is Int?
lkupType_2_ID = (model.SelectedTypeIDs.Length > 1) ? model.SelectedTypeIDs[1] : 0,
lkupType_3_ID = (model.SelectedTypeIDs.Length > 2) ? model.SelectedTypeIDs[2] : 0,
....
};
_context.Add(oOrder);
}
return RedirectToAction(....);
}
You can use the Length property in order to check the number of items in SelectedTypeIDs list.
if(model.SelectedTypeIDs.Length>3){
//code
}
If the condition is false you can use ModelState.AddModelError method in order to show the error in the View.
if(model.SelectedTypeIDs.Length>3){
ModelState.AddModelError("Dropdown", "Error! You must have maximum of 3 options");
return View();
}
UPDATE
You can create a generic function which returns 0, if the index is out of bound or the list item instead.
public static TValue GetSafe<TItem>(this IList<TItem> list,
int index, TValue defaultValue)
{
if (index < 0 || index >= list.Count)
{
return defaultValue;
}
return list[index];
}
Now you can use this function to implement you functionality.
var oOrder = new Order
{
orderName = model.orderName,
StartDate = model.StartDate,
EndDate = model.EndDate,
....
....
lkupType_1_ID =model.SelectedTypeIDs.GetSafe(0, 0) ,
lkupType_2_ID =model.SelectedTypeIDs.GetSafe(1, 0) ,
lkupType_3_ID =model.SelectedTypeIDs.GetSafe(2, 0) ,
....
};
Related
I have an input box in a View and i am trying to pass value from my controller.
Value is perfectly getting in line ViewBag.request_number = final, but in my view it is displaying null
public JobsController(AppDbContext context)
{
string prefix = "D";
string lastPRNo = "D0001";
string number = lastPRNo;
if (lastPRNo.Length < 3)
{
number = "000" + lastPRNo;
number = number.Substring(number.Length - 3, 3);
}
string final = prefix + number;
ViewBag.request_number = final;
_context = context;
}
My View code is below.
<div class="form-group col-md-6">
<label asp-for="JobNumber" class="control-label"></label>
<input id="JobNumber" asp-for="JobNumber" value=#ViewBag.request_number class="form-control" type="text" />
<span asp-validation-for="JobNumber" class="text-danger"></span>
</div>
Please try moving the code of getting Request Number to
public IActionResult YourView()
{
string prefix = "D";
string lastPRNo = "D0001";
string number = lastPRNo;
if (lastPRNo.Length < 3)
{
number = "000" + lastPRNo;
number = number.Substring(number.Length - 3, 3);
}
string final = prefix + number;
ViewBag.request_number = final;
_context = context; }
ViewBag has a dependency on ViewData and ViewData is populated after the controller is initialized.
This makes impossible to set ViewBag in the constructorbecause when the Controller's constructor is called, the properties for HttpContext, ControllerContext etc. are not set. They are only set after the constructor is called and there is a valid instance/reference to this object.
As you can read here
ViewBag code looks like this:
public dynamic ViewBag
{
get
{
if (_viewBag == null)
{
_viewBag = new DynamicViewData(() => ViewData);
}
return _viewBag;
}
}
And in the ViewData property you can see this:
This property can be accessed after the controller has been activated,
for example, in a controller action or by overriding OnActionExecuting(ActionExecutingContext)
I am developing an application where I need to pass the button value from view to controller to filter the DB.
Here I need to display years from 2009 to 2018/current year as button for that I used action link. I am not sure how to pass the year which I am getting in for loop to my controller to get only the clicked year results.
View :
var year = DateTime.Now.Year;
for (var i = year; i > 2009; i--)
{
var j = #i - 1;
<div class="col-md-1 ">
#Html.ActionLink(i.ToString(), "MyCases/" + i)
</div>
}
Controller :
public ActionResult MyCases(int i)
{
var cases = _db.case.Where(p => p.date.Value.Year == i)
.OrderByDescending(p => p.date).ToList();
return View(cases);
}
You can pass the year as the route values parameter of the ActionLink overload you are using. Pass an anonymous object with name same as your action method parameter name(i).
#Html.ActionLink(i.ToString(), "MyCases", new { i = i })
The ActionLinkhelper will build the correct markup for an anchor tag where your href attribute value will be like MyCases?i=2018 where 2018 is a sample value of i
You can use this overload to explicitly specify the controller name
#Html.ActionLink(i.ToString(), "MyCases","Home", new { i = i }, null)
I used the following tutorial:
http://msdn.microsoft.com/en-us/library/gg508808%28VS.98%29.aspx
And everything seemed fine, but in my case, string Username always comes back null. After tonnes of research, I found everyone discovered BIND Prefixes. That would be great in many circumstances, but not this one. I should note all properties and names line up, however in my for loop, the EditorFor creates a [i].Username field and this doesn't map to any model property.
QUESTION: I think I want to map [i].Username to Username where i is any number from 0-infinity, so when it GETS, the value is passed to the Action properly. How do I do this? If this is wrong, what do I do validate this for a specific row in a table?
#for (var i = 0; i < Model.Count; i++)
{
BLAH BLAH BLAH CODE FOR BUILDING TABLE ROWS
<td>
#Html.EditorFor(modelItem => Model[i].Username)
</td>
}
Since I could technically have HUNDREDS if not THOUSANDS of records, I would rather not had a binding PREFIX for all 1000. Am I fundamentally missing something here? I am new to ASP.NET MVC and am used to WebForms so I feel like sometimes I am mixing concepts and mashing up something that is entirely wrong.
EDIT:
I fixed it by doing the following, but not sure if this is the best idea. I set the parameter equal to the FieldName without [i] prefix, but still retrieve the element with the [i] prefix. Javascript isn't my Forte so please let me know if it is horrible.
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = fieldName.substr(fieldName.lastIndexOf(".") + 1);
var actualFieldName = appendModelPrefix(fieldName, prefix)
value.data[paramName] = function () {
return $(options.form).find(":input").filter("[name='" + escapeAttributeValue(actualFieldName) + "']").val();
};
});
setValidationValues(options, "remote", value);
});
You have not posted your code for the model or controller, but assuming you have a RemoteAttribute applied to property Username, for example
public class MyModel
{
[Remote("IsValidUserName", "Person")]
public string Username { get; set; }
}
with a method in PersonController
public JsonResult IsValidUserName(string Username)
{
....
}
and the view
#model List<Person>
...
#for (var i = 0; i < Model.Count; i++)
{
#Html.EditorFor(m => m[i].Username)
}
This will generate html such as
<input name="[0].UserName" ... />
<input name="[1].UserName" ... />
Unfortunately the remote method in jquery-validate posts back the name and value of the element so that the ajax call looks like
$.ajax({
url: '/Person/IsValidUserName',
data: { [0].UserName: 'someone#somewhere.com' },
...
which will not bind.
I have reported this as an issue at Codeplex with a possible solution. In the meantime you can modify the remote method in jquery-validate.js file as follows
remote: function(value, element, param) {
....
var data = {};
// data[element.name] = value;
data[element.name.substr(element.name.lastIndexOf(".") + 1)] = value; // add this
This will strip the prefix so that the posted data is
data: { UserName: 'someone#somewhere.com' },
and will correctly bind to the method.
Assuming the code is formatted in the following way:
View:
#for(var i = 0; i<Model.Count; i++) {
<div class="row">
#Html.EditorFor(modelItem => Model[i].Username)
</div>
}
<style>
.valid{
background: lime;
}
</style>
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
Model:
public class MyModel {
[Remote("IsValidUserName", "Validation", HttpMethod = "POST")]
public string Username { get; set; }
}
It is possible to use the automatic modelbinding to bind to the remote validation. If you were to use a list or array for this, the binding would fail while a Dictionary can catch this error.
Be aware however that the Key in the dictionary will be consistent with the id in the view (e.g. [5].Username will map to {Key: 5, Value: MyModel{Username:...}}) and won't be a default 0, hence the use of a Linq query.
Controller:
[HttpPost]
public JsonResult IsValidUserName(Dictionary<int,MyModel> Users) {
return Json(Users.First().Value.Username.Contains("User"));
}
I have a field called ReclaimTotalAmount that displays a value from a c# model.
<div class="container-left">
Reclaim Total: <span data-bind='text: model.ReclaimTotalAmount'></span>
</div>
And I also have a field that displays the value of the Sum of fields:
<div class="container-left">
Countered Total:<span data-bind='text: model.CounteredTotalAmount'></span>
</div>
To get the CounteredTotalAmount I use the following
self.model.CounteredTotalAmount = ko.computed(function () {
var SumCounterTotals = 0;
for (var i = 0; i < self.model.CounterReclaimViewModels().length; i++) {
SumCounterTotals += (
parseFloat(self.model.CounterReclaimViewModels()[i].CounteredTimeAmount())
+ parseFloat(self.model.CounterReclaimViewModels()[i].CounteredMileAmount())
+ parseFloat(self.model.CounterReclaimViewModels()[i].CounteredAppurtenanceAmount())
)
}
So I need to check weather the Countered total is greater than the ReclaimTotal. I tried this:
I created an extension
self.model.CounteredTotalAmount.extend({
greaterThan: { params: self.model.ReclaimTotalAmount, message: "Car number high must be greater than the low." }
});
then this is the function
ko.validation.rules['greaterThan'] = {
validator: function (val, other) {
if (val != null && val != "" && other != null) {
var first = parseInt(val);
var second = parseInt(ko.unwrap(other));
if (!isNaN(first) && !isNaN(second)) {
return first > second;
}
}
return true;
},
message: 'Must be greater than or equal to the other value'
};
everything works except the validation. I am not able to generate an error message if the Countered Total is greater than the Reclaim total...
Thanks
Multiple things could go wrong, but because you haven't' posted a complete example only code fragments you need to check the following things:
Because you are creating a custom validator you need to call ko.validation.registerExtenders(); before you want to use it for the first time.
the KO .extend({ }) returns the extended observable so you need to override the existing property with the result:
self.CounteredTotalAmount = self.CounteredTotalAmount.extend({
greaterThan: {
params: self.ReclaimTotalAmount,
message: "Car number high must be greater than the low."
}
});
Because KO validation only overrides the value and checked binding to automatically display the error message. So you need to use the validationMessage binding to display your error because you are using the text binding here:
<div class="container-left">
Countered Total:<span data-bind='text: CounteredTotalAmount'></span>
<span data-bind="validationMessage: CounteredTotalAmount"></span>
</div>
Here is a working JSFiddle with the simplified version of your code.
This behavior is making me wonder about my sanity..
I have a form that has two places that accept input, let's call them ValueA and ValueB. The user can enter a value in either one and the form submits.
<div id="MyUpdateTarget">
<% using (Ajax.BeginForm("MyControllerAction", new AjaxOptions { UpdateTargetId = "MyUpdateTarget" })) { %>
<%=Html.TextBox("ValueA", Model.ValueA, new Dictionary<string, object> {
{ "onchange", "$('#SubmitButton').click(); return false;" },
}) %>
<%=Html.TextBox("ValueB", Model.ValueB, new Dictionary<string, object> {
{ "onchange", "$('#SubmitButton').click(); return false;" },
}) %>
<input id="SubmitButton" type="submit" value="Save" style="display: none;" />
<% } %>
</div>
The Controller Action looks like this:
public ActionResult MyControllerAction(MyViewModel viewModel)
{
// do some other stuff ...
return PartialView("MyPartialView", viewModel);
}
The ViewModel is simply this:
public class MyViewModel
{
private int _valueA;
private int _valueB;
public int ValueA
{
get
{
return _valueA;
}
set
{
if (value > 0)
{
ValueB = 0;
}
_valueA = value;
}
}
public int ValueB
{
get
{
return _valueB;
}
set
{
if (value > 0)
{
ValueA = 0;
}
_valueB = value;
}
}
}
Now, the unexpected piece. Say the page initially loads and ValueB has a value of 7. The user changes ValueA to 5 and the form submits. I can put a breakpoint in the controller action and see both values in the viewModel parameter. At this point, ValueA is 5 and ValueB is 0 (due to the setting of ValueA). The action returns the viewModel as part of the PartialView. Back in the partial, I can put a breakpoint on the Html.TextBox("ValueB", Model.ValueB, ...) line and see that ValueB is indeed 0. But when the form renders to the browser, ValueB still has a value of 7. And this is where I am stuck. I have even changed the Update target to a different div, so that the partial just spits out the form someplace completely different, but it still has the original value of 7, even though I saw through debugging that the value was 0 coming back from the controller.
Is there something I am missing?
Here is the code from the MVC Source for the textbox:
string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : valueParameter**), isExplicitValue);
break;
And the Code for GetModelStateValue()
internal object GetModelStateValue(string key, Type destinationType) {
ModelState modelState;
if (ViewData.ModelState.TryGetValue(key, out modelState)) {
if (modelState.Value != null) {
return modelState.Value.ConvertTo(destinationType, null /* culture */);
}
}
return null;
}
So what happens is the Html "Helper" looks for the text box value, by matching the name, in your ViewData.ModalState, if its in the ModelState dictionary, it completely ignores the value you provided.
So all that if (value > 0) { ValueA = 0; } doesn't matter because its going to use the posted values in ModelState if the names match.
The way I've fixed this is to blow away the ModelState before the view renders for certain values that I want to mess with in my view models. This is some code I've used:
public static void SanitizeWildcards( Controller controller, params string[] filterStrings )
{
foreach( var filterString in filterStrings )
{
var modelState = controller.ModelState;
ModelState modelStateValue;
if( modelState.TryGetValue(filterString,out
controller.ModelState.SetModelValue(filterString, new ValueProviderResult("","", null));
}
}
Clearing the entire ModelState might also do the trick:
ViewData.ModelState.Clear();
thanks jfar.. this is the vb code:
Sub CleanForm(ByVal ParamArray Fields() As String)
Dim modelStateValue As ModelState = Nothing
For Each Field In Fields
If ModelState.TryGetValue(Field, modelStateValue) Then
ModelState.SetModelValue(Field, New ValueProviderResult(Nothing, Nothing, Nothing))
End If
Next
End Sub
As #jfar mentioned, removing the variable from the ModelState in your controller will do the trick. You can do that with less code, though (these days, at least).
ModelState.Remove("ValueA");