Passing different types to view - c#

I have a method in controler in which I pass to view objects with different type like:
case ItemType.Magnets:
{
return View(dbHelper.Magnets.Details(id));
}
case ItemType.Maps:
{
return View(dbHelper.Maps.Details(id));
}
How can I write a view for this?

You need to creates different views for different item. Here is solution
case ItemType.Magnets:
{
return View("MagnetsView",dbHelper.Magnets.Details(id));
}
case ItemType.Maps:
{
return View("Maps",dbHelper.Maps.Details(id));
}
If you want to display Magnets item you need to create Magnet view. Same for Maps.

You might want to create two different views, each called unique and each accepting its own viewmodel.
You will also want to specify view's name in the controller class:
return View("ViewName_1", dbHelper.Maps.Details(id));
or
return View("ViewName_2", dbHelper.Magnets.Details(id));
#AlexDefine unless you want to create a universal view for all types of properties inside, which is not that easy.
Part of it I have here:
#model object
<table>
#{
var properties = from prop in ViewData.ModelMetadata.Properties
let propInfo = prop.ContainerType.GetProperty(prop.PropertyName)
where !ViewData.TemplateInfo.Visited(prop)
select prop;
}
#foreach (var prop in properties)
{
<tr>
<td>
<div style="float: right;">
#prop.GetDisplayName()
</div>
</td>
<td>
<div class="editor-field">
#Html.Editor(prop.PropertyName, new { ContainerModel = Model })
</div>
</td>
</tr>
}
</table>
More or less, it makes a table of any kind of model. But you should upgrade it for your needs by self.

Related

Save multiple rows simultaneously from the same form - dotnet core

I have a table that has one empty column into which user can enter a comment:
Table
-----
TbMapId | UniqueAdp | Dealership | Line
--------------------------------------------------------------------
1 | [Insert comment here] | Derby | abc123
2 | [Insert comment here] | Keighley | cda345
3 | [Insert comment here] | Manchester | 876ghj
There is a lot of data to comment on, I can't expect a user to open an 'Edit' page and type in a comment one by one. Instead I need user to be able to input a bunch of comments (say 20 at a time against 20 rows) and save them all at one click of submit button.
If you want to jump straight to working solution go to EDIT #2 & look at Rudi's accepted answer
View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.TBMapBalancesList.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(Model => Model.TBMapBalancesList[i].TbMapId)
#Html.HiddenFor(Model => Model.TBMapBalancesList[i].TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.TBMapBalancesList[i].UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].Line)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalancesList[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Model
I've learned today that I need to use List to be able to iterate through the lines in table by the use of #for loop (as shown above). before I was trying to use IEnumerable. So I added a definition to the model for public List<TBMapBalances> TBMapBalancesList { get; set; }
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapBalances> TBMapBalances { get; set; }
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalancesList { get; set; }
[...]
}
Controller:
Now this is where I need the help with, my code doesn't throw any errors at all. When I press Submit nothing happens:
[Authorize]
[HttpPost]
public async Task<IActionResult> TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
try
{
_context.Update(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
}
return RedirectToAction("TbMapView");
}
EDIT #1
Changes to View
<form asp-action="TbMapViewEdit">
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#for (int i = 0; i < Model.TBMapBalances.Count; i++)
{
<tr>
<td>
#Html.DisplayFor(Model => Model.TBMapBalances[i].TbMapId)
#Html.HiddenFor(Model => Model.TBMapBalances[i].TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.TBMapBalances[i].UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].Line)</td>
<td>#Html.DisplayFor(Model => Model.TBMapBalances[i].MapResult)</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</form>
Changes to model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace ASPNET_Core_1_0.Models.TbMapViewModels
{
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; }
[...]
}
}
Changes to Controller:
Now this is where I need the help with, my code doesn't throw any errors at all when at the current state - when I press Submit nothing happens and no data gets saved to the database.
however, when you uncomment line _context.Update(tbMapViewModel.TBMapBalances); I get an error that List is not part of any Model and is not found.
Also, below code is something I wrote trying to follow this SO post: update-multiple-records-at-once-in-asp-net-mvc - Initially I was trying to make it Async but I was getting even more errors and couldn't continue. I thought I am going to follow it as closely as possible in hope that it will get me a working starting point.
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
// _context.Update(tbMapViewModel.TBMapBalances);
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
EDIT #2 - A hero to the rescue - big thanks to #RudiVisser for help
First of all if any of you guys are - like me - stuck using .net core 1.0.0
make sure you upgrade to the latest version first (1.1.7 lts). Part of my grief was that I was an USER 1.0 and did not upgrade my installation as fixes and additions kept coming out. Don't be that guy, like I was...
All below is thanks to Rudi's help:
View
#using (Html.BeginForm("TbMapViewEdit", "TbMap"))
{
<div class="col-lg-6">
<div class="row">
<input type="submit" value="Save" class="btn btn-primary" />
<div class="col-md-12">
<table class="table table-condensed table-bordered table-hover">
<thead>
<tr>
<td><b>TEMP ID</b></td>
<td><b>Map To</b></td>
<td><b>Accounts Code</b></td>
<td><b>Line</b></td>
<td><b>Map Result</b></td>
</tr>
</thead>
<tbody>
#Html.EditorFor(m => m.TBMapBalances);
</tbody>
</table>
</div>
</div>
</div>
}
Put your "Method", "Controller" in (Html.BeginForm("TbMapViewEdit", "TbMap")) otherwise the form POST action will be set to the current location.
Model
Truncated for brevity. I have view model with List that I will be saving the data to and one other table just for displaying some info.
public class TbMapViewModel
{
public IEnumerable<ASPNET_Core_1_0.Models.TBMapUniqueADP> TBMapUniqueADP { get; set; }
public List<TBMapBalances> TBMapBalances { get; set; } = new List<TBMapBalances>();
[...]
}
Controller
[Authorize]
[HttpPost]
public IActionResult TbMapViewEdit(TbMapViewModel tbMapViewModel)
{
if (ModelState.IsValid)
{
foreach (var TbListId in tbMapViewModel.TBMapBalances)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.UniqueAdp = TbListId.UniqueAdp;
}
}
_context.SaveChanges();
}
return RedirectToAction("TbMapView");
}
Error that I was making here is that I was trying to replace the key with essentially the copy of itself (Find ID of 1 and set it to ID of 1) instead of picking up on the actual one field that I needed to edit which in my case was UniqueAdp.
Then came the new thing to me, which was Editor Template:
Editor Template
Create a folder called EditorTemplates in 'Shared' Folder under your 'Views' folder with the exact name of the model that you intend to edit. In my case the model was called TBMapBalances so I created a TBMapBalances.cshtml file inside the newly created folder, then pasted this (this was originally in my main view file):
#model ASPNET_Core_1_0.Models.TBMapBalances
<tr>
<td>
#Html.DisplayFor(Model => Model.TbMapId)
#Html.HiddenFor(Model => Model.TbMapId)
</td>
<td>#Html.EditorFor(Model => Model.UniqueAdp, new { #class = "control-label_DI" })</td>
<td>#Html.DisplayFor(Model => Model.AccountsCode)</td>
<td>#Html.DisplayFor(Model => Model.Line)</td>
<td>#Html.DisplayFor(Model => Model.MapResult)</td>
</tr>
By the way the new { #class = "control-label_DI" } is there to supposedly add class to each created input field. This doesn't seem to work in .net core and is left there just as a reminder to myself that I need to do this somehow.
Research:
Update multiple records at once in asp.net mvc
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms
http://www.binaryintellect.net/articles/b1e0b153-47f4-4b29-8583-958aa22d9284.aspx
How to bind an Array in MVC Core
https://www.red-gate.com/simple-talk/dotnet/asp-net/model-binding-asp-net-core/
ASP.NET Core 1.0 POST IEnumerable<T> to controller
This problem has 2 parts to it, the first is that there needed to be a way to edit collections of data. This can be solved with EditorTemplates, which involves creating a single editor model and then calling #Html.EditorFor(..) on the collection of items you wish to edit.
A minimal sample (Full Fx, not Core) is available on Github.
The second problem was with the way the entities were being updated, the property being changed was wrong and hence not saving (the PK was being updated to the PK) and the entity was being attached when it's already tracked.
foreach (var TbListId in tbMapViewModel.TBMapBalancesList)
{
var getCode = _context.TBMapBalances.Where(p => p.TbMapId == TbListId.TbMapId).FirstOrDefault();
if (getCode != null)
{
getCode.TbMapId = TbListId.TbMapId;
}
}
try
{
_context.Update(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw;
}
It's important to remember what Entity Framework does for you when you retrieve a model from the database. It is automatically tracked by the context, and so it's already attached and ready to update, anything you change will be automatically tracked and subsequently saved.
The call to _context.Update(..) tries to attach the non-tracked models (from your POSTed data) to the context based on ID, but because you've already pulled that ID out (in your .Where(..).FirstOrDefault(..)) it's already tracked, and so blows up.
Also given that this is EFC 1.0 and you have no .Find(..) method, using .SingleOrDefault(..) is probably a better method to use on a primary key field.
Your rewritten code could be as so:
foreach (var postedModel in tbMapViewModel.TBMapBalancesList)
{
var dbModel = _context.TBMapBalances.SingleOrDefault(p => p.TbMapId == postedModel.TbMapId);
if (dbModel != null)
{
dbModel.UniqueAdp = postedModel.UniqueAdp;
}
}
await _context.SaveChangesAsync();
For posterity though I wouldn't recommend it for security reasons, if you wanted to attach your whole posted model to the context (based on ID) and update it, you can do so with code similar to your original, removing the foreach loop:
_context.UpdateRange(tbMapViewModel.TBMapBalances);
await _context.SaveChangesAsync();
(I don't recommend it because everything that was posted will then be set in the database, from experience it's advisable to only set the fields you're expecting to update as per the first code set. It should, however, be quicker than the foreach loop given that you're not loading from the database and saving back in, only the latter)
Do you have the inputs for the comments already built into the razor page? I do not see them. What you would want to do is create a form with the input types that you want for each item in the loop inside the loop. Each form would then reference the iterator as a hidden value to pass when posted. If the loop is foreach(var item in Model.items){} you would have a form containing the inputs in that block with a hidden input that looks like <input type="hidden" name="index" value="#item.index"/> This will allow you to post with whatever identifier you need to attach that specific form data to the correct model.
See this answer for the structure of the form inside the loop Multiple forms on one MVC form, created with a loop, only the first submits data

asp.net mvc viewdata display in a viewpage

I sent a viewdata to a view page like this.
public ActionResult SelectRes(int id)
{
var ResList = resrepository.GetAll();
ViewData["ResList"] = new SelectList(ResList, "ResId", "Res_KORNM");
return View(svcresrelationRepository.GetAll());
}
#foreach (var item in ViewData["ResList"] as List<ITSDapper.Dapper.Resource>)
{
<tr>
<td style="text-align:center;">
#Html.DisplayFor(a=> item.Res_KORNM)
</td>
</tr>
}
And I tried to display the viewdata in a view page but it's not worked.
(Object reference not set to an instance of an object)
How display the viewdata in foreach statement?
You would typically use a SelectList for data that is to be selected by a user.
If this is the intention you can just use an Html.DropDownList:
#Html.DropDownList("SelectedId",
(IEnumerable<SelectListItem>)ViewData["ResList"])
If you are simply needing to view the data, I would change your server-side code to use something other than a SelectList.
This could be done as follows:
Controller
public ActionResult SelectRes(int id)
{
var ResList = resrepository.GetAll();
ViewData["ResList"] = ResList;
return View(svcresrelationRepository.GetAll());
}
View
#foreach (var item in ViewData["ResList"] as List<ITSDapper.Dapper.Resource>)
{
<tr>
<td style="text-align:center;">
#Html.DisplayFor(a=> item.Res_KORNM)
</td>
</tr>
}
You are converting reference to wrong type, you are creating SelectList instance while in View you are convering it to IList<T>, you should convert it to SelectList:
#foreach (var item in ViewData["ResList"] as SelectList)
{
<tr>
<td style="text-align:center;">
#Html.DisplayFor(a=> item.Text) // note this as well
</td>
</tr>
}

How to correctly pass an object related to a jQuery ListView line to the specific controller method?

I have a jQuery view that show a list of Fix object, this one:
<ul data-role="listview" data-inset="true" data-theme="b" data-split-icon="delete">
#foreach (DataModel.Vulnerability.Fix item in Model.VulnerabilityFixes)
{
<li><a href="#Url.Action("Details", "Product", new { Title = item.Title })">
<h2>#item.Title</h2>
<table style="width: 100%">
<tr>
<th>Id</th>
<th>FixName</th>
<th>Vendor</th>
<th>Version</th>
</tr>
<tr>
<td>#MyHelper.decodeNull(item.Id)</td>
<td>#MyHelper.decodeNull(item.FixName)</td>
<td>#MyHelper.decodeNull(item.Vendor)</td>
<td>#MyHelper.decodeNull(item.Version)</td>
</tr>
</table>
</a>
Delete
</li>
}
</ul>
As you can see, this list contains a button (a link) to delete the related fix, this one:
Delete
When this link is clicked is called the DeleteFix() method of the EditingController class that take 2 paramethers:
vulnId that is a long value.
The Fix object related to the clicked link line.
So this is my DeleteFix() method,
public ActionResult DeleteFix(long vulnId, DataModel.Vulnerability.Fix currentFix)
{
DataModel.Vulnerability.Fix model = new DataModel.Vulnerability.Fix();
manager.openConnection();
try
{
model = currentFix;
}
finally
{
manager.closeConnection();
}
return View(model);
}
The problem is that the when enter into the method the vulnId parameter is correctly set but the currentFix parameter is null.
What am I missing? What can I do to correctly pass the Fix object related to the line on which I pressed the delete button?
You can't pass an "object" in a URL. You can only pass text. (Although you could serialize the object). You probably want to pass the Id of the item. Thus the link should look like:
Delete
You action should be:
public ActionResult DeleteFix(long vulnId, int currentFix)
Then you can use the Id to delete the item.

how I can have a controller, which can pass (return) some model to _Layout.cshtml. view

I have a file index.cshtml which is auto generated by EF (code first). Following code is working fine in that file
#model IEnumerable<dyescan.Models.MyMainMenuItem>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.ItemText)
</td>
<td>
#Html.DisplayFor(modelItem => item.LinkAction)
</td>
<td>
#Html.DisplayFor(modelItem => item.LinkController)
</td>
</tr>
}
But When I copy pasted the code in _layout.cshtml, I am unable to use this Model there.
Object reference not set to an instance of an object
I get above error in this line #foreach(var item in Model). Following is my code in _Layout.cshtml
#model IEnumerable<dyescan.Models.MyMainMenuItem>
#foreach(var item in Model)
{
<li>#Html.ActionLink(item.ItemText,item.LinkAction,item.LinkController)</li>
}
Update
So I tried to follow th comments this way. I made a Controller Shared (folder name) and in that controller I made an action _Layout (file name of the view). now it returns dbContex.MyMainMenuItems.. to the view
But still unable to make a controller for _Layout.. to pass it a model for my menuitems
When you use model in cshtml view, your Action method return model like :-
public ActionResult Index()
{
var items = themes.Select(o => new SelectListItem {Text = o, Value = o, Selected = o == theme});
return View(items);
}
When Action method return model it is bind to view. so in above code items will be bind to index.cshtml.
If you try to access this model in _Layout.cshtml it will give error.

#Html.Editor showing odd results when using reflected properties

I'm using a generic Razor view to allow any entity framework object to be edited. Here's a cut down version of it:
#model Object
#using (Html.BeginForm())
{
#foreach (var property in Model.VisibleProperties())
{
#Html.Label(property.Name.ToSeparatedWords())
#Html.Editor(property.Name, new { #class = "input-xlarge" })
}
}
And the VisibleProperties() function goes like this:
public static PropertyInfo[] VisibleProperties(this Object model)
{
return model.GetType().GetProperties().Where(info =>
(info.PropertyType.IsPrimitive || info.PropertyType.Name == "String") &&
info.Name != model.IdentifierPropertyName()).OrderedByDisplayAttr().ToArray();
}
(I'm reusing code from https://github.com/erichexter/twitter.bootstrap.mvc/)
One of my sample controllers goes as follows:
public ActionResult Edit(int id = 0)
{
TaskTemplate tasktemplate = db.TaskTemplates.Single(t => t.TaskTemplateID == id);
return View(tasktemplate);
}
Now the problem:
It all works fine except for where there's an ID property that relates to a 'parent' table, such as UserID. For these fields, the output of the #Html.Editor is simply:
FalseFalseFalseTrueFalse.
The True seems to correspond to the user in question - in this case the 4th user in the database.
Why is it not ouputting a nice textbox with the number 4 (or whatever the UserID) is in it?
I hope I've explained this clearly.
The reason for that is because editor/display templates are not recursing into complex child objects. If you want this to happen you could write a custom editor template for the object type (~/Views/Shared/Object.cshtml) as illustrated by Brad Wilson in this blog post (more specifically the Shallow Dive vs. Deep Dive section towards the end).
So:
<table cellpadding="0" cellspacing="0" border="0">
#foreach (var prop in ViewData.ModelMetadata.Properties.Where(pm => pm.ShowForEdit && !ViewData.TemplateInfo.Visited(pm)))
{
if (prop.HideSurroundingHtml)
{
#Html.Editor(prop.PropertyName)
}
else
{
<tr>
<td>
<div class="editor-label" style="text-align: right;">
#(prop.IsRequired ? "*" : "")
#Html.Label(prop.PropertyName)
</div>
</td>
<td>
<div class="editor-field">
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName, "*")
</div>
</td>
</tr>
}
}
</table>

Categories