I am using MVC 4 Visual Studio 2012 with Razor.
I am generating a table based off of several tables pulled into a dataset from a remote call to a SQL server.
I want to be able to output these tables onto the webpage and then create two columns of checkboxes beside them in order to assign them to one area or another (it essentially sorts data into accepted and not accepted, while allowing some to continue pending if a decision has not been made).
I currently have all the tables in the dataset being assigned to datatables in the controller and then exported to the razor page. I do not have a model set up for this as of yet and I'm not sure what I would require in one if I did.
This is my current View:
#{
ViewBag.Title = "Requisitions";
}
<table class="table">
<thead>
<tr>
#foreach (System.Data.DataColumn col in Model.Columns)
{
<th class ="td">#col.Caption</th>
}
</tr>
</thead>
<tbody>
#foreach(System.Data.DataRow row in Model.Rows)
{
<tr>
#foreach (var cell in row.ItemArray)
{
<td class="td">#cell.ToString()</td>
}
</tr>
}
</tbody>
</table>
This is my current controller:
DataTable R = new DataTable();
public void GetData()
{
string connString = "Data Source=.;database=dataBase;Integrated Security=SSPI";
DataSet dataset = new DataSet();
SqlDataAdapter adapter = new SqlDataAdapter("dbo.procApprovalSelectPending", connString);
using (adapter)
{
adapter.SelectCommand.CommandType = CommandType.StoredProcedure;
adapter.Fill(dataset);
}
int count = dataset.Tables.Count;
for (int i = 0; i < dataset.Tables.Count; i++)
{
// Do something for each recordset (11 recordsets)
if (i == 0)
{
R = dataset.Tables[i];
}
}
dataset.Dispose();
adapter.Dispose();
}
public ActionResult Rs()
{
GetData();
return View(R);
}
I have more datatables than shown but they were removed to conserve space.
To sum it up, I would like two checkbox columns that are aligned with each row that is created from the datatable that allow me to choose that portion of data to send back to the server what has been changed with it, and creating a dynamic checkbox for each row was giving me an error such that :
#foreach(System.Data.DataRow row in Model.Rows)
{
<tr>
#foreach (var cell in row.ItemArray)
{
<td class="td">#cell.ToString()</td>
}
<td class="td">#Html.CheckBoxFor(m => m.Checkbox)</td>
</tr>
}
"An expression tree may not contain a dynamic operation"
Thanks in advance!
You should create a view model to represent the properties you want to display, including 2 additional boolean properties for 'accepted' and 'not accepted'. Assuming your columns are ID and Name
public class MyModel
{
public int ID { get; set; }
public string Name { get; set; }
public bool Accepted { get; set; }
public bool NotAccepted { get; set; }
}
and create a collection based on each row in your table that you want to display
public ActionResult Rs()
{
List<MyModel> items = new List<MyModel>();
// Populate items from your datatable
return View(items);
}
then in your view
#model List<MyModel>
#using (Html.BeginForm()
{
<table>
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.TextBoxFor(m => m[i].ID)</tr>
<td>#Html.TextBoxFor(m => m[i].Name)</tr>
<td>#Html.CheckBoxFor(m => m[i].Accepted)</tr>
<td>#Html.CheckBoxFor(m => m[i].NotAccepted)</tr>
</tr>
}
</table>
<input type="submit" value="Save" />
}
then in your post method
[HttpPost]
public ActionResult Rs(List<MyModel> model)
{
foreach(MyModel item in model)
{
if(item.Accepted) {.. // do something
else if (item.NotAccepted) { .. // do something else
else {.. // do another thing
}
}
Use Following pattern
I have Created one static Class that is called as CommonUtilities it is giving me FormCollection,Checkboxname and valueofKey
public static class CommonUtilities
{
public static List<string> GetCheckboxSelectedValue(FormCollection frm, string checkboxname, string value)
{
List<string> retls = new List<string>();
var fileIds = frm[value].Split(',');
var selectedIndices = frm[checkboxname].Replace("true,false", "true").Split(',').Select((item, index) =>
new {
item= item,
index= index
}).Where(row=>row.item =="true")
.Select(row => row.index).ToArray();
if (selectedIndices.Count() > 0)
{
retls.AddRange(selectedIndices.Select(index => fileIds[index]));
}
return retls;
}
}
the above Function will be applied as below
Controller
List<string> selectedKeypair = CommonUtilities.GetCheckboxSelectedValue(frm, "m.BoolSelectedVal", "m.Key");
view
#foreach (MVCCareerModule.Models.Requirement m in Model.RequirementSearchResult)
{
<tr>
<td>
#Html.CheckBoxFor(modelitem => m.Apply)
#Html.HiddenFor(modelitem => m.Req_ID, new { Value = m.Req_ID })
</td>
</tr>
}
You will receive all Selected IDs in selectedKeypair
Related
I ran into an issue with server-side Blazor trying to create a custom table component. The data in the table rows updates and changes dynamically so that is not the issue but if I bind the header on a property, the header will take the previous value of that property.
From table.razor, I am setting up a simple dropdown <select> tag with default values. When that value is changed, it should update the value on the table header.
I have added a <code> tag and a classic HTML table as a test and they both reflect the new <select> value properly. Any idea why it's not the same for a custom component?
Table.razor
#page "/table"
#using BlazorApp1.Data
#inject WeatherForecastService ForecastService
<select #bind="#SelectedListItem">
<option value="test">Test</option>
<option value="test2">Test2</option>
</select>
<code>#SelectedListItem</code>
<table>
<thead>
<tr>
<th>#SelectedListItem</th>
</tr>
<tbody>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
<tr>
<td>5</td>
</tr>
</tbody>
</thead>
</table>
#if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<BlazorApp1.Components.DataTable Items=#forecasts TRowItem="WeatherForecast">
<BlazorApp1.Components.Column CustomTitle="#SelectedListItem" TRowItem="WeatherForecast"></BlazorApp1.Components.Column>
</BlazorApp1.Components.DataTable>
}
#code{
public string SelectedListItem { get; set; }
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
}
}
DataTable.cs
using Microsoft.AspNetCore.Components;
namespace BlazorApp1.Components
{
public partial class DataTable<TRowItem> : ComponentBase
{
[Parameter]
public IList<TRowItem> Items { get; set; } = new List<TRowItem>();
[Parameter]
public RenderFragment? ChildContent { get; set; }
private IList<Column<TRowItem>> Columns { get; set; } = new List<Column<TRowItem>>();
protected override void OnInitialized()
{
if (Items == null) Items = new List<TRowItem>();
}
protected override async Task OnParametersSetAsync()
{
await UpdateAsync().ConfigureAwait(false);
}
public async Task UpdateAsync()
{
Refresh();
}
public void Refresh()
{
InvokeAsync(StateHasChanged);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
foreach (var column in Columns)
{
column.StateChanged += ColumnStateChanged;
}
StateHasChanged();
}
}
public void Dispose()
{
foreach (var column in Columns)
{
column.StateChanged -= ColumnStateChanged;
}
Items.Clear();
}
public void AddColumn(Column<TRowItem> column)
{
Columns.Add(column);
StateHasChanged();
}
private void ColumnStateChanged(Object? sender, EventArgs args) => StateHasChanged();
}
}
DataTable.razor
#typeparam TRowItem
<h3>DataTable</h3>
<CascadingValue Value="this">
<div>
<table>
<thead>
<tr>
#foreach(var column in Columns)
{
<th nowrap>#column.CustomTitle</th>
}
</tr>
</thead>
<tbody>
#foreach(var item in Items)
{
foreach(var column in Columns)
{
<td></td>
}
}
</tbody>
<tfoot>
</tfoot>
</table>
</div>
#ChildContent
</CascadingValue>
Column.cs
using Microsoft.AspNetCore.Components;
using System.Linq.Expressions;
namespace BlazorApp1.Components
{
public partial class Column<TRowItem> : ComponentBase
{
[CascadingParameter]
private DataTable<TRowItem>? DataTable { get; set; }
[Parameter]
public string? CustomTitle { get; set; }
[Parameter]
public Expression<Func<TRowItem, object>>? Property { get; set; }
protected override Task OnInitializedAsync()
{
if (DataTable == null) throw new ArgumentNullException($"A 'DataTableColumn' must be a child of a 'DataTable' component");
DataTable.AddColumn(this);
return Task.CompletedTask;
}
public event EventHandler? StateChanged;
private void RaiseStateChanged()
{
EventHandler? handler = StateChanged;
handler?.Invoke(this, new EventArgs());
}
}
}
Column.razor
#typeparam TRowItem
Any idea why it's not the same for a custom component?
Any change in your select causes a Blazor UI event in the page which triggers a re-render event. The Renderer does this by triggering SetParametersAsync on the component The component updates its parameters, runs OnParametersSet{Async} and re-renders.
DataTable contains Column so it re-renders first. At this point CustomTitle in Column hasn't run SetParametersAsync, so DataTable renders the current "old" value. Column then re-renders setting CustomTitle to the new value.
Based on the code you've shown the answer is to render the label in the Column component. But I'm guessing that's a bit more complex in reality, so that's probably not the answer. You probably want it to show the title in the header section and the value in the row template. If so I can point you to some code that shows how to do that.
The solution is not to do some more plumbing or put in some more StateHasChanged, but to rethink what your doing. Get the data i.e. your table configuration into a data class/classes and cascade that down to your individual components. Or use a DI service to hold the data and drive the component updates by events.
I am attempting to create a dynamic table in my view that will be dynamically generated depending on the type of model I send to the view. So, I basically have two actions:
public IActionResult People()
{
List<Person> lst = new List<Person>();
// Add data...
return View("Table", lst);
}
public IActionResult Teams()
{
List<Team> lst = new List<Team>();
// Add data...
return View("Table", lst);
}
Now I would like to have the same view that will show a list of people / teams, so that I don't have to duplicate it. My Table.cshtml looks like this:
#model List<dynamic>
<table>
<tr>
#foreach (var item in Model.ElementAt(0).GetType().GetProperties())
{
<td>
#item.Name
</td>
}
</tr>
#foreach (var item in Model)
{
<tr>
// foreach (var propValue in item.GetProperties())
// Get value for each property in the `item`
</tr>
}
</table>
My basic output would be an HTML table corresponding to what is shown below:
Id, PersonName, Age
1, John, 24
2, Mike, 32
3, Rick, 27
What I have a problem with is dynamically get the value for each property in my model class instance. I don't know how to get the value from the item (there's no such thing as item.Property(someName).GetValue()). That way I could send a List (T could be Person, Team, Student, anything) and as a result I would get a <table> with Person / Team / Student properties (e.g Id, Name, Age) and values of each of the properties in another <tr>.
It comes to errors when I use #model List<dynamic> as model of view.When I change it to #model dynamic,it works with below code
#model dynamic
#using System.Reflection
#{
var properties = Model[0].GetType().GetProperties();
}
<table>
<tr>
#foreach (var item in properties)
{
<td>
#item.Name
</td>
}
</tr>
#foreach (var item in Model)
{
<tr>
#foreach (PropertyInfo p in properties)
{
<td>#p.GetValue(item)</td>
}
</tr>
}
</table>
#model List<dynamic>
<table>
<tr>
#foreach (var item in Model.ElementAt(0).GetType().GetProperties())
{
<td>
#item.Name
</td>
}
</tr>
#foreach (var item in Model)
{
if (item.GetType() == typeof(Person))
{
var person = item as Person;
<tr>
#person.Name
</tr>
}
if (item.GetType() == typeof(Team)) {
var team = item as team;
<tr>
#team.Name
</tr>
}
}
</table>
So I'm new to ASP and EF and I am wondering how to do this incredibly basic operation, as well as a few questions to go along with doing it.
Currently I have a table we will call Resource;
class Resource
{
int current;
int min;
int max;
};
Right now I have the default CRUD options for this. What I would like is a + / - button on the main list that will manipulate the current value of each resource and update the value in the DB and on screen.
There are also certain operations I'd like to run such as "AddFive" to a selected group of resources.
So my questions;
How do I do this?
Is this scalable? If someone is constantly hitting the buttons this is obviously going to send a lot of requests to my DB. Is there any way to limit the impact of this?
What are my alternatives?
Thanks.
Edit:
To clarify the question; here are my post functions. How / where do I add these in my view to get a button showing for each resource. I just want the action to fire and refresh the value rather than redirect to a new page.
#Html.ActionLink("+", "Increment", new { id = item.ID })
public void Increment(int? id)
{
if (id != null)
{
Movie movie = db.Movies.Find(id);
if (movie != null)
{
Increment(movie);
}
}
}
[HttpPost, ActionName("Increment")]
[ValidateAntiForgeryToken]
public ActionResult Increment([Bind(Include = "ID,Title,ReleaseDate,Genre,Price")] Resource resource)
{
if ((resource.Current + 1) < (resource.Max))
resource.Current++;
return View(resource);
}
It sounds like the main issue you are having is creating a list of movies on the front end and updating the details for a specific one.
The key here is that you will need to either wrap a form around each item and have that posting to your update controller or use ajax / jquery to call the controller instead.
I have given you an example of the first one. Once the update controller is hit it will redirect to the listing page which will then present the updated list of movies.
Below is a minimal working example of how to wire this up. I've not included any data access code for brevity but have included psuedo code in the comments to show you where to place it.
Please let me know if you have any futher questions.
Controller
public class MoviesController : Controller
{
public ViewResult Index()
{
// Data access and mapping of domain to vm entities here.
var movieListModel = new MovieListModel();
return View(movieListModel);
}
public ActionResult Increment(IncrementMovieCountModel model)
{
// Put breakpoint here and you can check the value are correct
var incrementValue = model.IncrementValue;
var movieId = model.MovieId;
// Update movie using entity framework here
// var movie = db.Movies.Find(id);
// movie.Number = movie.Number + model.IncrementValue;
// db.Movies.Save(movie);
// Now we have updated the movie we can go back to the index to list them out with incremented number
return RedirectToAction("Index");
}
}
View
#model WebApplication1.Models.MovieListModel
#{
ViewBag.Title = "Index";
}
<h2>Some Movies</h2>
<table>
<tr>
<td>Id</td>
<td>Name</td>
<td>Increment Value</td>
<td></td>
</tr>
#foreach (var movie in Model.MovieList)
{
using (Html.BeginForm("Increment", "Movies", FormMethod.Post))
{
<tr>
<td>#movie.Id #Html.Hidden("MovieId", movie.Id)</td>
<td>#movie.Name</td>
<td>#Html.TextBox("IncrementValue", movie.IncrementValue)</td>
<td><input type="submit" name="Update Movie"/></td>
</tr>
}
}
</table>
Models
public class MovieListModel
{
public MovieListModel()
{
MovieList = new List<MovieModel> {new MovieModel{Id=1,Name = "Apocalypse Now",IncrementValue = 3}, new MovieModel {Id = 2,Name = "Three Lions", IncrementValue = 7} };
}
public List<MovieModel> MovieList { get; set; }
}
public class MovieModel
{
public int Id { get; set; }
public string Name { get; set; }
public int IncrementValue { get; set; }
}
public class IncrementMovieCountModel
{
public int IncrementValue { get; set; }
public int MovieId { get; set; }
}
I am trying to find a way to access specific cells in a DataTable in View. The DataTable was created in controller.
Code in conroller:
[ChildActionOnly]
public ActionResult _ls()
{
var getXMLlivescore = new HtmlDocument();
getXMLlivescore.Load("D://lscopy.xml");
DataTable matchTable = new DataTable();
matchTable.Columns.Add("put2forEventOr1", typeof(int));
matchTable.Columns.Add("country", typeof(string));
...
matchTable.Columns.Add("min", typeof(string));
matchTable.Columns.Add("extramin", typeof(string));
foreach (HtmlNode match in category.SelectNodes(".//match")){
//code to get xml tags
matchTable.Rows.Add(put2forEventOr1, country, ....., min, extramin);
}
return PartialView(matchTable);
}
and the partialView code:
<table>
#foreach (DataRow row in Model.Rows)
{
//get cell in row[0]
#if (row[0] == 3){
do some work
}
}
</table>
How can I iterate through DataTable cells in view and get specific cells?
I really don't understand why do you need to use a DataTable. You can always create a class with structure you need. It will be much easier to us simple POCO's in your views.
You haven't provided your XML so I made some examples for very simple version:
<Elements>
<Element>
<Country>Peru</Country>
<Min>20</Min>
</Element>
<Element>
<Country>Armenia</Country>
<Min>9</Min>
</Element>
</Elements>
For such XML you can create a class that will represent an Element:
public class Element
{
public string Country { get; set; }
public int Min { get; set; }
public string NotXmlProperty { get; set; }
}
And then you can use your method of reading XML or for example this one:
var xDoc = XDocument.Load(xmlFilePath);
IEnumerable<Element> elements = xDoc
.Descendants("Element")
.Select(x => new Element
{
Country = x.Element("Country").Value,
Min = Convert.ToInt32(x.Element("Min").Value),
NotXmlProperty = "Value"
});
After that accessing your data in the view should be very simple:
#model IEnumerable<Element>
<table>
#foreach(var element in Model)
{
<tr>
<td>#element.Country</td>
<td>#element.Min</td>
<td>#element.NotXmlProperty</td>
</tr>
}
<table>
What about if I have a navigation property? I have a collection of another model in the model I'm trying to edit. I want to display a list of checkboxes for each object in the collection property. So far, this is what I tried...
#{
foreach (var category in ViewBag.Categories)
{
if (Model.Categories.Contains(category))
{
<input type="checkbox" name="selected-categories" value="category.CategoryId" checked="checked" />#category.Name
}
else
{
<input type="checkbox" name="selected-categories" value="#category.CategoryId" />#category.Name
}
}
}
But it fails with an EntityCommandExecutionException. In my if statement, how can I access the model.Categories like I do in something like #Html.EditorFor(model => model.Id)???
Using a strongly-typed View is the way I'd go. Create a ViewModel that contains your Model and use this ViewModel for the strongly-typed View.
Domain Model and ViewModel (simplified)
public class YourModel
{
string Category { get ; set ; }
}
public class YourViewModel
{
public List<string> PossibleCategories { get ; set ; }
public YourModel YourData { get ; set ; }
}
Then the View:
#model YourViewModel
#{
foreach (string CurrCategory in Model.PossibleCategories)
{
if (Model.YourData.Category == CurrCategory)
{
#Html.CheckBox(CurrCategory, new { #checked = "checked" })
#Html.Encode(CurrCategory) <br />
}
else
{
#Html.CheckBox(CurrCategory, false)
#Html.Encode(CurrCategory) <br />
}
}
}