Using a repeater without declaring data sources in views - c#

I have the following set up for my aspx page called "Default":
public partial class _Default : Page
{
IProjectRepository projectRepository = new SPProjectRepository();
protected void Page_Load(object sender, EventArgs e)
{
ViewModel = new HomeViewModel()
{
Projects = projectRepository.GetProjects
};
}
public HomeViewModel ViewModel { get; set; }
}
On the aspx page, I have the following which works great:
<table>
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody>
<% foreach( var project in ViewModel.Projects ) { %>
<tr>
<td><%:project.Title%></td>
</tr>
<% } %>
</tbody>
</table>
For learning purposes I wanted to know how I would achieve this with a repeater...
How do I go about using the repeater much in the same way I have written out direct html code with a foreach loop?

I am not sure why you want to skip the traditional DataSource approach. But if you are using ASP.NET 4.5 then you can use the SelectMethod (introduced in 4.5 version). Advantage of using this is you can define the Type by which your repeater control will bind and in templates you will get intellisense support.
Here is a simple example:-
Suppose you have Customer type:-
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
And if you want a list of customers to Repeater control then you can simply define a method and assign it to repeater control. No extra code like Datasource, DataBind required.
public IEnumerable rptCustomer_GetData()
{
return new List<Customer>
{
new Customer { Id =1, Name = "xx" },
new Customer { Id =2, Name = "yy" }
};
}
And in repeater just provide that method name:-
<asp:Repeater ID="rptCustomer" runat="server" SelectMethod="rptCustomer_GetData"
ItemType="Customer">
<ItemTemplate>
<asp:Label ID="lblId" runat="server" Text='<%# Item.Id %>'></asp:Label>
<asp:Label ID="lblName" runat="server" Text='<%# Item.Name %>'></asp:Label>
</ItemTemplate>
</asp:Repeater>
Traditional Data Source Approach:-
In Page Load Simply set the Data Source:-
rptCustomer.DataSource = projectRepository.GetProjects;
rptCustomer.DataBind();
And in mark-up simply use ASP.NET data binding Code nuggets like this:-
<asp:Label ID="lblId" runat="server" Text='<%# Id %>'>

Try the following:
Markup
<table>
<thead>
<tr>
<th>Title</th>
</tr>
</thead>
<tbody>
<asp:Repeater ID="ProjectsRepeater" runat="server">
<ItemTemplate>
<tr>
<td><%# Eval("Title") %></td>
</tr>
</ItemTemplate>
</asp:Repeater>
</tbody>
</table>
C#
public partial class _Default : Page
{
IProjectRepository projectRepository = new SPProjectRepository();
protected void Page_Load(object sender, EventArgs e)
{
ProjectsRepeater.DataSource = projectRepository.GetProjects;
ProjectsRepeater.DataBind();
}
public HomeViewModel ViewModel { get; set; }
}

Related

Blazor table component not updating dynamically

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.

Pass the name of a component when clicked

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; }
}

View - dynamic model

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>

Adding Content to database using MVC3

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"?

ASP.Net binding attributes of a custom control's inner property

I have created a UserControl with an Inner Property called "Actions", which is a List of "Action" objects. The code looks like this:
[ParseChildren(true)]
public class MyLink : UserControl
{
readonly List<Action> _actions = new List<Action>();
[PersistenceMode(PersistenceMode.InnerProperty)]
public List<Action> Actions
{
get { return _actions; }
}
public string Text { get;set; }
public string Url { get;set; }
public string MenuName { get; set; }
protected override void Render(HtmlTextWriter writer)
{
//Build link
StringBuilder sb = new StringBuilder();
sb.Append(#"
<table class=""myLink"">
<tr>
<td class=""myLinkLeft"">" + Text + #"</td>
<td class=""myLinkRight " + MenuName + #"_trigger""> </td>
</tr>
</table>
");
//Build actions
sb.Append("<ul id=\"" + MenuName + "_actions\" class=\"contextMenu\">");
foreach (Action action in _actions)
{
sb.Append("<li class=\"" + action.CssClass + "\">" + action.Text + "</li>");
}
sb.Append("</ul>");
writer.Write(sb.ToString());
}
}
public class Action : UserControl
{
public string Url { get; set; }
public string Text { get; set; }
public string ImageUrl { get; set; }
public string CssClass { get; set; }
}
If I then put this code in my aspx inside a DataRepeater, it works fine:
<uc1:MyLink runat="server" Url="/" Text='<%#DataBinder.Eval(Container.DataItem,"Text") %>' MenuName="contextMenu" id="contextMenu">
<Actions>
<uc1:Action runat="server" Url="http://mysite.com" Text="MyUrl" />
<uc1:Action runat="server" Url="http://google.com" Text="Google" />
</Actions>
</uc1:MyLink>
However, if I try to bind data to the attributes of the Action elements like so:
<uc1:MyLink runat="server" Url="/" Text='<%#DataBinder.Eval(Container.DataItem,"Text") %>' MenuName="contextMenu" id="contextMenu">
<Actions>
<uc1:Action runat="server" Url='<%#DataBinder.Eval(((RepeaterItem)Container.Parent).DataItem,"Url") %>' Text="MyUrl" />
<uc1:Action runat="server" Url="http://google.com" Text="Google" />
</Actions>
</uc1:MyLink>
I merely get the actual text "<%#DataBinder.Eval(((RepeaterItem)Container.Parent).DataItem,"Url") %>" assigned to the Url property, and not the evaluated server expression as I expected.
I've googled this for hours but cannot seem to find anybody else trying to do this. Any ideas why this isn't working and how to get around it?
Thanks,
Bjoern
you set the DataRepeater.Datasource in your aspx to a collection or a list static or get from database ...
instead of using DataRepeater try to make a loop inside that list you re already must create it and create new dynamic Action and that in page_load or page_init
Action a;
foreach(object x in objects)
{
a= new Action();
a.Url = ... ;
a.Text = ... ;
MyLink.Actions.Add(a);
}
Regards
I ended up using tokens for the innermost databinding and then handling the replacement in my control on bind. So the ASPX code looks like this:
<uc1:MyLink runat="server" Url="/">
<Actions>
<uc1:Action Url="/Page.aspx?cpr=##cpr##&opgaveId=##id##" />
<uc1:Action Url="/Test.aspx" />
</Actions>
</uc1:MyLink>
And the added CS code like this:
protected override void OnInit(EventArgs e)
{
DataBinding += BindData;
}
public void BindData(object sender, EventArgs e)
{
MyLink pl = (MyLink) sender;
IDataItemContainer container = (IDataItemContainer) pl.NamingContainer;
foreach (Action action in _actions)
{
action.Url = ReplaceTokens(action.Url, container);
action.Text = ReplaceTokens(action.Text, container);
}
}
private static string ReplaceTokens(string text, IDataItemContainer container)
{
Regex re = new Regex("##.*?##", RegexOptions.Compiled | RegexOptions.Singleline);
StringBuilder sb = new StringBuilder(text);
foreach (Match m in re.Matches(text))
{
string tokenValue = DataBinder.GetPropertyValue(container.DataItem, m.Value.Substring(2, m.Value.Length - 4), "{0}");
sb.Replace(m.Value, tokenValue);
}
return sb.ToString();
}

Categories