How can I change the view output based on a value? - c#

When I get a list of my recipes from the API to the MVC, I have a some columns like totalTimeUnitId that have integer values instead of their actual unit(grams, kg, etc.). I am trying to figure out how to write code so that instead of 1, my view will return, for example 'g'.
I have little to no experience at all in working with views and I could not figure out a way to accomplish this.
<td>
#Html.DisplayFor(modelItem => item.recipeIngredientUnitId)
</td>
For example in the code above, I would suppose that I need an #if clause that checks if item.recipeIngredientUnitId is 1, then if it is 2 and so on and it will eventually display the right unit. However, the syntax is quite an impediment for me and a little help will be very much appreciated.

Try the following:
To connect your model and the View:
In the View (.cshtml) file
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers // imports tag helpers
#model Recipe //this way you import your model to view
//then use the property of this model to display `totalTimeUnitId`
<td>
#Model.totalTimeUnitId
</td>
In Controller class
public IActionResult SomeName()
{
Recipe r = new Recipe();
return View(r); //you need to pass an instance of Recipe type
}

#Pete's code helped me out.
I added this in my Model class:
private int _totalTimeUnitId;
public int TotalTimeUnitId
{
get
{
return _totalTimeUnitId;
}
set
{
_totalTimeUnitId = value;
switch (_totalTimeUnitId)
{
case 1:
TotalTimeUnit = "min";
break;
case 2:
TotalTimeUnit = "hour(s)";
break;
default:
TotalTimeUnit = string.Empty;
break;
}
}
}

Related

Testing a x=> value in a HiddenFor line in a C# .NET view?

I have a form in a view in a C# ASP.NET MVC project that due to a bug in an earlier js cropping module occasionally ends off having a minus 1 (-1) in the value of the 'CropY' field.
Rather than trying to debug the cropper I thought I could just check for the -1 and make it a zero in the view, here:
#model UI.Models.PVModel
...
#Html.HiddenFor(x => x.CropY)
However, I don't seem to be able to modify the HiddenFor to set a value or 0 depending on if the value is >-1 or not, say with
#Html.HiddenFor(x => (x.CropY < 0 ? 0 : x.CropY))
As this (and all other combos I tried) gives me an error ( 'Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.').
I tried altering the model value itself before the form on the view with
model.CropY = (model.CropY < 0 ? 0 : model.CropY)
But that doesn't alter the value in the HiddenFor form field (I'm a beginner to C# and .NET so forgive me if this is a fundamental error)
Next I tried altering the model itself to test for this
public int CropY
{
set { CropY = value; }
get { return (CropY < 0 ? 0 : CropY); }
}
But I can't get this to work (System.StackOverflowException: 'Exception of type 'System.StackOverflowException' was thrown.'), even if it is a viable method and I confess I don't know the correct format here!
Any idea please? Thanks.
--- edit after discussions below, this is more of what has been tried ---
Thanks. Okay, So very similarly, I have a basic get;set; in my model:
public class ProductViewModel
{
public int CropY { get; set; }
Then in my controller, it's a post as it's x in y number of forms/pages that go one after another:
[HttpPost]
[Route("Edit")]
[ValidateAntiForgeryToken]
public ActionResult Edit(ProductViewModel model, string nav)
{
model.CropY = (model.CropY < 0 ? 0 : model.CropY)
...
return View(view, model);
}
Then in my view I have this:
#model edie.UI.Models.ProductViewModel
#{
ViewBag.Title = "Step 2/";
}
<form action="#Url.Action(null, "MyPages")" method="post" class="form" novalidate="novalidate">
#Html.AntiForgeryToken()
#Html.HiddenFor(x => x.CropY)
And the value, when I view the page, is still coming through as -1. If I set breakpoints and step the code and check I can see the line in the controller is being triggered and setting CropY to 0, and the view is returned. On the view itself I can see the Model.CropY is 0. But the HiddenFor(x => x.CropY) inserts -1!
I suppose I'm missing something...
I would personally avoid implementation of setters and getters in Model/ViewModel objects, of course, it can helps in special cases but let's keep it simple. So, first of all, i would create simple view model object like this:
public class IndexViewModel
{
public int CropY { get; set; }
}
I would move the business logic from the view to the controller. In perfect world it should be moved to some service (SOC). So my get method in the home controller would look like this:
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
var value = -2;
var viewModel = new IndexViewModel();
if(value < 0)
{
viewModel.CropY = 0;
}
else
{
viewModel.CropY = value;
}
return View(viewModel);
}
}
And the Index.cshtml:
#model WebApplication1.Models.Home.IndexViewModel
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#Html.HiddenFor(x => x.CropY)
value = -2; it's a variable which stores value that you receives (I suppose) from some data source like repository.
After some help from #stephen-muecke I found an answer. The issue is with with the modelstate. See TextBoxFor displaying initial value, not the value updated from code and TextBoxFor displaying initial value, not the value updated from code
So two of the methods I tried above did actually work (in a way) - altering the get; set; to handle the negative value and adding a check and update of the value in the controller before I return the view.
In the view itself the Model.CropY contained the correct 0 value then but the #Html.HiddenFor(x => x.CropY) still had the bad -1 value, and so added it to the form.
So the helper HiddenFor helper gets its value from the ModelState value, and not the model I was trying to alter and edit on the view.
Confused? I still am, but adding this in my POST controller fixed it:
ModelState.Clear();
You can also do a ModelState.Remove("SomeText"), so I'm advised this might be a better option that clearing the entire modelstate.

ASP.NET MVC 4.5 Map Partial View to Main View on Form submit

Hello Mighty Stackoverflowers,
I'm currently working on an ASP.NET MVC 4.5 application. I need to map the input values from my partial view to my main View Model, when I submit the create form.
In my View "Create.cshtml" I call a partial view "_SwotPart.cshtml". I pass a part of my ViewModel to the Partial View, like this:
Create.cshtml:
#model MyCoolApp.BLL.Models.MainVm
#foreach (var swot in Model.Swots)
{
<tr>
#foreach (var swotPart in swot.SwotParts)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", swotPart)
}
</tr>
}
My partial View looks as follows, _SwotPartial.cshtml :
<td class="form-group">
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
</td>
Anyways, when I submit my form, the values from the partial view never arrive in the controller.
Do you have any ideas how to map this properly?
Thanks!
The problem is in the input names that will be generated the way you're currently trying to achieve this. Razor needs the context of the entire list, or at least the item's position in it, in order to generate correct input names. In other words, the easiest way to solve your issue (with a caveat) is:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
#for (var j = 0; j < Model.Swots[i].SwotParts.Count(); j++)
{
if (Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHelpful || Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHarmful)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", Model.Swots[i].SwotParts[j])
}
}
...
Then, the partial has the correct context to work with and your inputs will be named like Swots[0].SwotParts[0].Label, which the modelbinder will be able to work with.
However, the caveat here is that you're splitting this list into two loops. That's still not going to work, as you're effectively messing with the overall context of the item(s) position within the model. To fix that, you should split your list in your model, which is better anyways, as you can remove this business logic from your view:
public class SwotVm
{
...
public List<SwotPartVm> InternalSwotParts { get; set; }
public List<SwotPartVm> ExternalSwotParts { get; set; }
}
Then, you can simply iterate over each list individually, and the values will naturally post back to the appropriate list.
Given that you're using a partial to render fields for a particular class type, though, you'd be better served by creating an editor template. If you simply move your partial code to the view: Views\Shared\EditorTemplates\SwotPartVm.cshtml, then in your main view, you can just do:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
<tr>
<th class="swot-heading">Internal</th>
#Html.EditorFor(m => m.Swots[i].InternalSwotParts)
</tr>
<tr>
<th class="swot-heading">External</th>
#Html.EditorFor(m => m.Swots[i].ExternalSwotParts)
</tr>
}
That's obvious much cleaner, and you can take this concept even further by adding a SwotVm.cshtml editor template, allowing you replace even this little bit of code with just:
#Html.EditorFor(m => m.Swots)
Note: In your SwotVm.cshtml editor template, you would only include the code for a single SwotVm. In other words, not including the for statement.
In order for your application to parse the posted values and properly and bind it to your view model. Names of posted form data needs to be like.
swots[x1].swotParts[x2].label
Where x1 is a number ranging from 0 and up for each swot.
Where x2 is a number ranging from 0 and up for each swot part in swots.
Now when you are posting, the form data names is just label.
Instead of :
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
try :
<textarea name="swots[x1].swotParts[x2].label" class="form-control" placeholder="#Model.SwotTypeId.GetLabel()" >#Model.Label</textarea>
Don't forget to replace the x1 and x2 with a number.
You can read more about model bindings to collections here.
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

How to bind a list of complex objects to view model on post in mvc?

I am creating x numbers of dropdownlist on the view and want to bind the selected value back to the view model on post. I have seen Scott's post and tried to write my code but still can't get it to work.
My view model consists of two items. One is for the name label for dropdownlist and one is for selected value. I need the selected value because I need to pre-select the dropdownlist.
VM
public class ColumnMapperVm
{
public string Column { get; set; }
public string SelectedValue { get; set; }
}
The view take a list of view model. The reason is because i don't know the number of dropdownlist that i need to have on the design time. It varies to users.
#model List<ColumnMapperVm>
#for (int i = 0; i < Model.Count; i++ )
{
<tr>
<td style="width: 50%;">
#Html.DisplayFor(m => m[i].Column)
</td>
<td>
#Html.DropDownList("SelectedValue", new SelectList(ViewBag.Columns, "StaticName", "DisplayName", Model[i].SelectedValue))
</td>
</tr>
}
I put the selectlist in a viewbag coz i want to keep my view model clern and i don't select list won't get posted back to the server.
And here is my controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult MapColumns(List<ColumnMapperVm> colmapper)
{
//colmapper is null
}
Of course, I got the values in FormCollection but model binder isn't binding the view model and I wonder how to make it work.
One probable cause might be the fact that currently all dropdowns on post add values with the same name to the request. To work this around you can use DropDownListFor:
#Html.DropDownListFor(m => m[i].SelectedValue, new SelectList(ViewBag.Columns, "StaticName", "DisplayName"))
This should ensure all dropdowns have correct name in HTML markup.

Determine the type of an action method argument recieving data from strongly typed view

I have the following problem - I am developing an ASP.NET MVC 3 application and I have view which is strongly typed. Because of the complexity of the data the model in the view looks like this :
#model List<List<DataAccess.MCS_DocumentFields>[]>
Then I render the view like this :
#using (Html.BeginForm("ActionMethodName", "Forms"))
{
<table border="1">
<tbody>
#for (int i = 0; i < Model.Count(); i++)
{
if (Model[i][0][0].ContentTypeId == 1)
{
#Html.Partial("_PartialHeader", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 2)
{
#Html.Partial("_PartialDrawing", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 3)
{
#Html.Partial("_PartialBody", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 4)
{
#Html.Partial("_PartialFooter", Model[i])
}
}
</tbody>
</table>
<button type="submit">Save</button>
}
and this is one of my partial views :
if (string.IsNullOrEmpty(item.FieldValue))
{
<td colspan="2">
#Html.DisplayFor(y => y[i][0].QuestionText)
#Html.HiddenFor(y => y[i][0].QuestionText)
</td>
}
else
{
<td colspan="2">
#Html.DisplayFor(y => y[i][0].QuestionText)
#Html.HiddenFor(y => y[i][0].QuestionText)
:
#Html.DisplayFor(y => y[i][0].FieldValue)
#Html.HiddenFor(y => y[i][0].FieldValue)
</td>
}
This is just a snippet, what I want to say is that my table has at least 8-9 rows and almost each row has #Html.HiddenFor so I expect to get data when I'm submiting the form.
In my controller I have this method :
[HttpPost]
public ActionResult ActionMethodName(List<MCS_DocumentFields>[] collection)
{
var test = collection;
List<MCS_Documents> model = DocumentService.All().ToList();
return View("Index", model);
}
I tried a lot of different types for the collection argument. Most of the time I get null, at best I get the first two row (No idea why exactly the first two) but nothing more. If I use FormCollection then I have all my submitted data, but it's not related to my MCS_DocumentFields class in any way.
I don't know what I'm doing wrong. I don't think that the type for the method should be guessed I think it must be determined by something and if there is some problem in code (most possibly in the partial view) because I've posted here almost all of my main view, then any suggestions why my logic is not working and why I can't bind the data to the original type of the model?
I suggest you to look at the BeginCollectionItem package (http://nuget.org/packages/BeginCollectionItem/), allthough I can see that your model is really complex (3 dimensions), sou you'll have to nest them in a really nasty manner.
Also you should consider (due to your model complexity) creating a custom model binder (http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder).
Just one last remark - your model in View differs from your model that you expect in action:
List<MCS_DocumentFields>[] collection != List<List<DataAccess.MCS_DocumentFields>[]>
Ivan

MVC3 Razor View Engine

I just started to learn MVC3. Have been coding in traditional ASP.NET For a while and now would like to move to MVC.
Some things that i don't understand (probably just been used differently then in traditional ASP.NET)
I'm trying to write a simple news module that will display news and allow to insert comment for them.
So first step is, i created a tables on my SQL server:
TblNews
TblCategories
TblComments
Created Linq2SQL data Class in Models folder and named it News.dbml
Created Controller HomeController.cs and a method called Index() in it.
Look like this:
public ActionResult Index()
{
Models.NewsDataContext db = new Models.NewsDataContext();
var Model = (from n in db.TblNews
select new
{
ID = n.ID,
Title = n.Title,
Description = n.Description,
Category = n.TblCategory.CategoryName
});
return View(Model);
}
As you see I'm trying to select all the news and their category names (TblNews and TblCategories do have relationship between them)
After that I'm returning the data that i got from the query.
In the View i have:
#{
ViewBag.Title = "News Index Page";
}
<table>
#foreach (var item in Model)
{
<tr>
<td>
ID: #item.ID<br />
Title: #item.Title<br />
Description: #item.Description<br />
Category: #item.Category
</td>
</tr>
}
</table>
Which should return something like:
ID: 4
Title: asd
Description: asd
Category: 2
That's my sample data from tables.
When i run the page it gives me error message:
'object' does not contain a definition for 'ID'
but when i focus my mouse on "item" variable it actually contain:
{ ID = 4, Title = asd, Description = asd, Category = Test2 }
I also tried to return Linq Query as list by adding .ToList() to the end of it.
Can anybody give me any hints and get me into the right direction?
Sorry if i explained something incorrectly. English is not my primary language.
Please ask if you need any more information.
Thank you very much.
P.S. I'm using Visual Studio 2012
You're missing your model declaration at the top of your view so will default to type object. When you do item.ID you're doing object.ID, which is why it complains that object does not contain a definition for ID
Add the following:
#model List<Models.NewsDataContext>
Also, make sure you evaluate the LINQ query by calling .ToList() before your return Model, that way the query is already executed against the database before it hits the view:
return View(Model.ToList());
Edit: Actually, in your LINQ query you are selecting into an anonymous type, you must use a concrete class in order to use this in your view. The below assumes your TblNews corresponds to a class called News:
var Model = (from n in db.TblNews
select new News //class name here
{
ID = n.ID,
Title = n.Title,
Description = n.Description,
Category = n.TblCategory.CategoryName
});
return View(Model.ToList());
Then change your model to:
#model List<News>

Categories