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>
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.
How would I go about passing the name of a component that is clicked through the onclick handler in Blazor WebAssembly? For example (In this case we'll use a table cell):
InterestingTable.razor
<table class="table">
<thead>
<tr>
<th scope="col">First</th>
<th scope="col">Second</th>
</tr>
</thead>
<tbody>
<tr>
<td #onclick="#(e => CellClicked(e, ?????))" name1="firstTd"><SomeComponent name2="firstComponent" /></td>
<td #onclick="#(e => CellClicked(e, ?????))" name1="secondTd"><SomeComponent name2="secondComponent" /></td>
</tr>
</tbody>
</table>
InterestingTableBase.cs
protected void CellClicked(MouseEventArgs e, ????) {
}
How would I pass either the string in name1 or name2 into the onclick handler?
Thanks!
In javascript when a dom event handler is invoked, the this keyword inside the handler is set to the DOM element on which the handler is registered. In Blazor this is not the case. You either have to create a component or if you have to use native elements store the values in the code section so that you can access them in the event handler.
Index.razor:
<div name="#myDivName" #onclick="MyHandler2"></div>
<Component Name="Foo" OnClick="MyHandler"></Component>
#code{
private string myDivName = "foo";
public void MyHandler2()
{
}
public void MyHandler(string name)
{
}
}
Component.razor:
<div name="#Name" #onclick="()=>OnClick.InvokeAsync(Name)"></div>
#code {
[Parameter]
public string Name { get; set; }
[Parameter]
public EventCallback<string> OnClick { get; set; }
}
I am using ASP.NET MVC 4 Runtime, and writing encrypted data to my database using a function.
public string Encrypt(string encryptMe)
{
return Helpers.Encryptor.Encrypt(encryptMe);
}
On return the data is displayed like so:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Phone)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
I want to decrypt the data before displaying it here using the following function:
public static string Decrypt(string decryptMe)
{
return Helpers.Encryptor.Decrypt(decryptMe); ;
}
How can I decrypt my data?
Add a Property in the model called for example "DecryptedPhone".
This will only have a Get and will call the decrypting function:
public string DecryptedPhone
{
get
{
return Decrypt(Phone);
}
}
private string Phone;
The phone will be private so that it cannot be accessed.
In your controller's method, before the return View(model); line, you can decrypt the properties. It would be something like this:
foreach (var item in model)
{
item.Phone = Decrypt(item.Phone);
item.Name = Decrypt(item.Name);
}
return View(model);
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
I have some stored procedure to add information to a database already. Now I am creating a webpage that allows a user to view certain items within the database. When a user view the page, he or she have the option to edit/update or add a new merchant. I am having difficulties in the controller my method is not taking in the parameters im giving it. If you have any clues or know the answer please share.. Thank You
P.S Whener I hover over the add function it says
bool MerchantTypeRef.addMerchantTypeRef(MerchantAdminProductionServices.MerchantTypeRef merchantTypeRef)
Error:
The best overloaded method match for 'MerchantAdministrator.Models.MerchantAdminProduction.MerchatTypeRef.addMerchantTypeRef(MerchantAdministrator.MerchantAdminProductionServices.MerchantTypeRef)' has some invalid arguments
Controller
[MerchantAuthorizeAttribute(AdminRoles = "AddMerchantTypeRef")]
public ActionResult AddMerchantTypeRef()
{
try
{
Guid merchantTypeRefId = Request["merchantTypeRefId"] != null ? new Guid(Request["merchantTypeRefId"]) : Guid.Empty;
string name = Request["name"]?? string.Empty;
string description = Request["description"]?? string.Empty;
string xMerchantType = Request["xMerchantTypeRefCode"]??string.Empty;
DarkstarAdministrator.DarkstarAdminProductionServices.MerchantTypeRef merchantTypeRef = new DarkstarAdministrator.DarkstarAdminProductionServices.MerchantTypeRef();
merchantTypeRef.name = name;
merchantTypeRef.description = description;
merchantTypeRef.xMerchantTypeCode = xMerchantType;
ViewBag.addMerchantTypeRef = MerchantAdministrator.Models.MerchantAdminProduction.MerchantTypeRef.addMerchantTypeRef(merchantTypeRef); <------This where I have the Trouble . not reading parameter
}
catch (Exception e)
{
Commons.ErrorHandling.ReportError("MerchantAdministrator.Controllers.ProdController AddMerchantTypeRef()", e);
}
return View();
}
Model
public static bool addMerchantTypeRef(DarkstarAdminProductionServices.MerchantTypeRef merchantTypeRef)
{
try
{
DarkstarAdminProductionServices.DarkstarAdminProductionServicesSoapClient client = new DarkstarAdminProductionServices.DarkstarAdminProductionServicesSoapClient();
return client.addMerchantTypeRef(merchantTypeRef);
}
catch (Exception e)
{
Commons.ErrorHandling.ReportError("MerchantTypeRef.addMerchantTypeRef()", e);
}
return false;
}
Reference
[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
public System.Guid merchantTypeRefId {
get {
return this.merchantTypeRefIdField;
}
set {
if ((this.merchantTypeRefIdField.Equals(value) != true)) {
this.merchantTypeRefIdField = value;
this.RaisePropertyChanged("merchantTypeRefId");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false)]
public string name {
get {
return this.nameField;
}
set {
if ((object.ReferenceEquals(this.nameField, value) != true)) {
this.nameField = value;
this.RaisePropertyChanged("name");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false, Order=2)]
public string description {
get {
return this.descriptionField;
}
set {
if ((object.ReferenceEquals(this.descriptionField, value) != true)) {
this.descriptionField = value;
this.RaisePropertyChanged("description");
}
}
}
[System.Runtime.Serialization.DataMemberAttribute(EmitDefaultValue=false, Order=3)]
public string xMerchantTypeCode
{
get {
return this.xMerchantTypeCodeField;
}
set {
if ((object.ReferenceEquals(this.xMerchantTypeCodeField, value) != true)) {
this.xMerchantTypeCodeField = value;
this.RaisePropertyChanged("xMerchantTypeCode");
}
}
}
View
<script type="text/javascript">
$(document).ready(function () {
$("#merchantTypeUpdateButton").click(function () {
$("#updateMerchantType").submit();
});
});
Edit Merchant Type
<%MerchantAdministrator.MerchantAdminProductionServices.MerchantTypeRef EditMerchantType = ViewBag.MerchantTypeRefEdit !=null ? ViewBag.MerchantTypeRefEdit: new MerchantAdministrator.DarkstarAdminProductionServices.MerchantTypeRef(); %>
<form id="updateMerchantType" action="<%=Url.Action("EditMerchantTypePost","Prod") %>? merchantTypeRefId"=<%=EditMerchantType.merchantTypeRefId %>" method="post">
<table>
<tr>
<td colspan="3" class="tableHeader">Merchant Type Ref Details</td>
</tr>
<tr>
<td colspan="2" class="label">Name:</td>
<td class="content">
<input type="text" maxlength="100" name="Name" value=" <%=EditMerchantType.name %>" />
</td>
</tr>
<tr>
<td colspan="2" class="label">Description:</td>
<td class="content">
<input type="text" maxlength="2000" name="Description" value="<%=EditMerchantType.description %>" />
</td>
</tr>
<tr>
<td colspan="2" class="label">Merchant Type Code:</td>
<td class="content">
<input type="text" maxlength="5" name="XMerchantTypeCode" value="<%=EditMerchantType.xMerchantTypeCode %>" />
</td>
</tr>
<tr>
<td colspan="3" class="tableFooter">
<br />
<a id="merchantTypeUpdateButton" href="#" class="regularButton">Save</a>
Cancel
</td>
</tr>
</table>
bool ViewBag.addMerchantTypeRef = MerchantAdministrator.Models.MerchantAdminProduction.MerchantTypeRef.addMerchantTypeRef(merchantTypeRef);
Can you please tell me is this "merchantTypeRef" or "merchantTypeRefId"? Because merchantTypeRefId is what read by the first line and the same value will need to be passed when you call Model. If that doesn't work, can you please try with "FormCollection"?