Blazor binding parent/child dropdown controls fails to set default value on child control - blazor-server-side

On a parent/child dropdown scenario, I'm trying to display a default value for the child dropdown each time the parent dropdown is updated.
The code does the job, since it updates the sample label, but the child dropdown does not display anything.
For simplicity I am demonstrating the issue with a country/zone pair of select controls.
Initial render correctly shows the default country and the default zone of the country.
Updating the country sets the new default zone on the label but the zones select turns blank:
#page "/test"
<select #bind="#CountryName">
#foreach (var country in Countries)
{
<option>#country.Name</option>
}
</select>
<select #bind="#ZoneName">
#foreach (string zone in Zones)
{
<option>#zone</option>
}
</select>
<div>Selected country: #CountryName</div>
<div>Selected zone: #ZoneName</div>
#code {
private List<Country> Countries;
private List<String> Zones = new();
private string countryName;
private string CountryName
{
get => countryName;
set
{
countryName = value;
var country = Countries.First(x => x.Name == value);
Zones = country.Zones.Select(x => x.Name).ToList();
ZoneName = country.DefaultZoneName;
}
}
private string zoneName;
private string ZoneName
{
get => zoneName;
set
{
zoneName = value;
}
}
protected override void OnInitialized()
{
//data factory
var barcelona = new Zone() { Name = "Barcelona" };
var madrid = new Zone() { Name = "Madrid" };
var spain = new Country() { Name = "Spain" };
spain.Zones.Add(barcelona);
spain.Zones.Add(madrid);
spain.DefaultZoneName = spain.Zones.Last().Name;;
var açores = new Zone() { Name = "Açores" };
var algarve = new Zone() { Name = "Algarve" };
var portugal = new Country() { Name = "Portugal" };
portugal.Zones.Add(açores);
portugal.Zones.Add(algarve);
portugal.DefaultZoneName = portugal.Zones.Last().Name;
Countries = new List<Country>();
Countries.Add(spain);
Countries.Add(portugal);
CountryName = Countries.Last().Name;
base.OnInitialized();
}
//models
public class Country
{
public string Name { get; set; }
public List<Zone> Zones { get; set; } = new();
public string DefaultZoneName { get; set; }
}
public class Zone
{
public string Name { get; set; }
}
}

The problem is that set method on variable doesn't cause the page to re-render: automatic re-render occurs with a Task handling an #event, and forced re-render occurs with StateHasChanged.
You are trying to use set method because 2-way binding on the select consumes the #onchange event, so you have no way to do anything when a value is selected. However, this is a trap. Blazor allows C# boolean for selected (and also for other things like disabled or hidden).
You already took some time to set up your data nicely, so you can track selection by reference, not by copying the string values of names to new variables.
I think the following is an improvement. I hope it helps you, as this same situation will come up very often in Blazor:
#page "/"
<select #onchange="(args) => SelectedCountry = Countries.First(x => x.Name==args.Value?.ToString())">
#foreach (var country in Countries)
{
<option selected="#(country == SelectedCountry)" >#country.Name</option>
}
</select>
<select #onchange="(args) => SelectedCountry.SelectedZone = SelectedCountry.Zones.First(x => x.Name==args.Value?.ToString())">
#foreach (var zone in SelectedCountry.Zones)
{
<option selected="#(zone == SelectedCountry.SelectedZone)">#zone.Name</option>
}
</select>
<div>Selected country: #SelectedCountry.Name</div>
<div>Selected zone: #SelectedCountry?.SelectedZone?.Name</div>
#code {
private List<Country> Countries = new();
private Country SelectedCountry = new();
protected override void OnInitialized()
{
//data factory
var barcelona = new Zone() { Name = "Barcelona" };
var madrid = new Zone() { Name = "Madrid" };
var spain = new Country() { Name = "Spain" };
spain.Zones.Add(barcelona);
spain.Zones.Add(madrid);
spain.SelectedZone = spain.Zones.Last();
var açores = new Zone() { Name = "Açores" };
var algarve = new Zone() { Name = "Algarve" };
var portugal = new Country() { Name = "Portugal" };
portugal.Zones.Add(açores);
portugal.Zones.Add(algarve);
portugal.SelectedZone = portugal.Zones.Last();
Countries = new List<Country>();
Countries.Add(spain);
Countries.Add(portugal);
SelectedCountry = portugal;
}
//models
public class Country
{
public string Name { get; set; }
public List<Zone> Zones { get; set; } = new();
public Zone? SelectedZone;
}
public class Zone
{
public string Name { get; set; }
}
}

Related

MudBlazor Mudtable RowEditing Datetime

I use from mudblazor the MudTable. It displays the data perfectly.
I like to edit it inline. In my model there is a DateTime field I want to edit and set.
After some juggling around, I see a datepicker, a timepicker, but I can't seem to figure out how to edit this inline as a legal date and time format and combine them back to the data-sourcefield.
Can you help me with some samplecode to achieve that.
Thank you.
Inside the <MudTd> tag, add the MudDatePicker and MudTimePicker:
<MudTd>
<MudDatePicker
Date="#context.DateTime.Date"
DateChanged="#(d => this.DateChanged(d.Value, context))"/>
<MudTimePicker
Time="#context.DateTime.TimeOfDay"
TimeChanged="#(t => this.TimeChanged(t.Value, context))"/>
</MudTd>
and implement the DateChanged(...) and TimeChanged(...) in your own code.
A demo:
#page "/"
<MudTable Items="#this.elements">
<ToolBarContent>
<MudText Typo="Typo.h6">Elements</MudText>
</ToolBarContent>
<HeaderContent>
<MudTh>Id</MudTh>
<MudTh>Name</MudTh>
<MudTh>DateTime</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>#context.Id</MudTd>
<MudTd>#context.Name</MudTd>
<MudTd>
<MudDatePicker Date="#context.DateTime.Date" DateChanged="#(d => this.DateChanged(d.Value, context))"/>
<MudTimePicker Time="#context.DateTime.TimeOfDay" TimeChanged="#(t => this.TimeChanged(t.Value, context))"/>
</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager />
</PagerContent>
</MudTable>
<MudText Color="Color.Primary">#this.log</MudText>
#code {
private string log = string.Empty;
private List<Element> elements = new();
protected override void OnInitialized()
{
this.elements = new List<Element>
{
new() { Id = 1, Name = "First", DateTime = DateTime.UtcNow - TimeSpan.FromDays(1) },
new() { Id = 2, Name = "Second", DateTime = DateTime.UtcNow - TimeSpan.FromDays(2) },
new() { Id = 3, Name = "Third", DateTime = DateTime.UtcNow - TimeSpan.FromDays(3) },
};
}
public class Element
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateTime { get; set; }
}
private void DateChanged(DateTime date, Element element)
{
this.SetDateTime(date, element.DateTime.TimeOfDay, element);
}
private void TimeChanged(TimeSpan time, Element element)
{
this.SetDateTime(element.DateTime.Date, time, element);
}
private void SetDateTime(DateTime date, TimeSpan time, Element element)
{
element.DateTime = new DateTime(date.Year, date.Month, date.Day, time.Hours, time.Minutes, time.Seconds);
this.log = $"New datetime for element with Id {element.Id}: {element.DateTime}";
}
}
Try it online: https://try.mudblazor.com/snippet/caGROlvFgrXENaMD

MVC Core DropDownList selected value ignored

I am trying to access my page at: https://localhost:44319/Analyze/Index/6
The problem is that my drop down list always selects the first item in the list instead of the one provided by ID. While stepping through the debugger, before the View() is returned, I see that the SelectList was populated correctly.
AnalyzeController.cs
public IActionResult Index(int? Id)
{
return Index(Id ?? getStatementEndingById(Id).StatementEndingId);
}
[HttpPost]
public IActionResult Index(int StatementEndingId)
{
var statementEnding = getStatementEndingById(StatementEndingId);
ViewBag.StatementEndingId = new SelectList(
_context.StatementEnding.OrderByDescending(s => s.StatementEndingId),
"StatementEndingId",
"Name",
statementEnding);
return View(getPayments(statementEnding));
}
private StatementEnding getStatementEndingById(int? statementEndingId)
{
StatementEnding statementEnding;
if (statementEndingId.HasValue)
{
statementEnding = _context.StatementEnding.FirstOrDefault(s => s.StatementEndingId == statementEndingId);
}
else
{
statementEnding = _context.StatementEnding.OrderByDescending(s => s.StatementEndingId).FirstOrDefault();
}
return statementEnding;
}
Setting DropDownList in Razor
#Html.DropDownList("StatementEndingId", null, new { #class = "form-control mb-2 mr-sm-2" })
I am using ASP.NET Core 2.1.
Any suggestions are much appreciated. Thanks in advance.
First i would recomend to create a typed model, something like this one :
public class StatementViewModel
{
public int StatementEndingId { get; set; }
public List<SelectListItem> StatementEndings { get; set; }
}
Second fill the Model with all dropdown options (StatementEndings) and the selected one (StatementEndingId)
public IActionResult Index()
{
var model = new StatementViewModel();
model.StatementEndingId = getStatementEndingById(Id).StatementEndingId;
model.StatementEndings = _context.StatementEnding.OrderByDescending(s => s.StatementEndingId).Select(p => new SelectListItem() { Text = p.Name, Value = p.StatementEndingId }).ToList();
return View(model);
}
And for the last, in the view
#model StatementViewModel
#Html.DropDownListFor(m => m.StatementEndingId, Model.StatementEndings, null, new { #class = "form-control mb-2 mr-sm-2" })

RavenDB static index on dictionary

I have an application that uses documents, that contain list of attributes in a dictionary, for some reason we need to use a static index and query/filter over these attributes.
A prototype looks like this:
class Program
{
static void Main(string[] args)
{
IDocumentStore store = new DocumentStore() { DefaultDatabase = "Test", Url = "http://localhost:8081" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Program).Assembly, store);
using (var session = store.OpenSession())
{
session.Store(new Document { Id = "1", Name = "doc_name", Attributes = new Dictionary<string, object> { { "Type", "1" }, { "Status", "Active" } } });
session.SaveChanges();
}
using (var session = store.OpenSession())
{
// works
var l1 = session.Query<Document, Documents_Index>().Where(a => a.Attributes["Type"] == "1").ToList();
// not working
var l2 = session.Query<Document, Documents_Index>().Where(a => a.Attributes["Status"] == "Active").ToList();
}
}
}
public class Documents_Index : AbstractIndexCreationTask<Document>
{
public Documents_Index()
{
Map = docs => docs.Select(a =>
new
{
a.Name,
a.Attributes,
Attributes_Type = a.Attributes["Type"]
});
}
}
[Serializable]
public class Document
{
public string Id { get; set; }
public string Name { get; set; }
public Dictionary<string, object> Attributes { get; set; }
}
But since I need to query using any arbitrary Attribute name/value this index does solve our problem. Actually the list of attributes is known at run-time (so we tried modifying the Map expression to inject any number of attribute names, but so far we weren't successful). Is there a way how to define the index in some dynamic fashion?
You need to write it like:
public class Documents_Index : AbstractIndexCreationTask<Document>
{
public Documents_Index()
{
Map = docs => docs.Select(a =>
new
{
a.Name,
_ = a.Attributes.Select(x=>CreateField("Attributes_"+x.Key, x.Value),
});
}
}

RavenDB lazy search against Index returns uninitialized statistiscs

I am trying to run lazy queries against raven db and get the counts on total matching results. I am finding when I query against a static index, a lazy search does not initialize the statistics when the query is materialized, but otherwise it comes back all right.
Below is the test to prove this behaviour.
[TestFixture]
public class CanSearchLazily
{
private const int ServerPort = 8085;
private readonly string _serverAddress = #"http://localhost:{0}".For(ServerPort);
[Test]
public void CanGetTotalResultsFromStatisticsOnLazySearchAgainstDynamicIndex()
{
CanGetTotalResultsFromStatisticsOnLazySearchAgainstAnIndex();
}
[Test]
public void CanGetTotalResultsFromStatisticsOnLazySearchAgainstStaticIndex()
{
CanGetTotalResultsFromStatisticsOnLazySearchAgainstAnIndex("UserByFirstName");
}
private void CanGetTotalResultsFromStatisticsOnLazySearchAgainstAnIndex(string indexName = "")
{
BuilderSetup.DisablePropertyNamingFor<User, string>(x => x.Id);
var users = Builder<User>.CreateListOfSize(2000).All()
.With(x => x.FirstName = GetRandom.FirstName())
.With(x => x.LastName = GetRandom.LastName())
.Build();
using (GetNewServer())
using (var store = new DocumentStore { Url = _serverAddress }.Initialize())
{
using (var session = store.OpenSession())
{
users.ForEach(session.Store);
session.SaveChanges();
IndexCreation.CreateIndexes(typeof(UserByFirstName).Assembly, store);
session.Query<User, UserByFirstName>().Customize(x => x.WaitForNonStaleResults()).ToList();
}
using (var session = store.OpenSession())
{
var names = session.Query<User>().Select(u => u.FirstName).Distinct().Take(15).ToList();
RavenQueryStatistics stats;
var query = string.IsNullOrEmpty(indexName)
? session.Query<User>().Statistics(out stats).Where(x => x.FirstName.In(names))
: session.Query<User>(indexName).Statistics(out stats).Where(x => x.FirstName.In(names));
var results = query.Take(8).Lazily();
Assert.AreEqual(8, results.Value.ToList().Count);
Assert.AreEqual(DateTime.Now.Year, stats.IndexTimestamp.Year, "the index should have the current year on its timestamp");
Assert.IsTrue(stats.TotalResults > 0, "The stats should return total results");
}
}
}
protected RavenDbServer GetNewServer(bool initializeDocumentsByEntitiyName = true)
{
var ravenConfiguration = new RavenConfiguration
{
Port = ServerPort,
RunInMemory = true,
DataDirectory = "Data",
AnonymousUserAccessMode = AnonymousUserAccessMode.All
};
if (ravenConfiguration.RunInMemory == false)
IOExtensions.DeleteDirectory(ravenConfiguration.DataDirectory);
var ravenDbServer = new RavenDbServer(ravenConfiguration);
if (initializeDocumentsByEntitiyName)
{
using (var documentStore = new DocumentStore
{
Url = _serverAddress
}.Initialize())
{
new RavenDocumentsByEntityName().Execute(documentStore);
}
}
return ravenDbServer;
}
}
[Serializable]
public class User
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserByFirstName : AbstractIndexCreationTask<User>
{
public UserByFirstName()
{
Map = users => from user in users
select new {user.FirstName};
}
}

How do I test "Parameters Passed In Final Call" using RhinoMocks?

What's the best way in Rhino Mocks to test that a particular parameter is passed on the FINAL call to a method? Eg mockview.SetSomething(myObj) might be called any number of times by mockview.Init, but I want to test that the last time it's called as mockview.SetSomething(inParticular).
Now I know I can use GetArgumentsForCallsMadeOn with this, but my problem is that it doesn't work if I've subsequently changed the parameter holding variable. e.g
public interface IView
{
void SetSomething(ViewData data);
}
public class ViewData
{
public int Age { get; set; }
public string Name { get; set; }
public ViewData Person(int age, string name)
{
Age = age;
Name = name;
return (this);
}
}
public class WorkingPresenter
{
public void Init(IView view)
{
var data = new ViewData {Age = 1, Name = "One"};
view.SetSomething(data);
data = new ViewData {Age = 2, Name = "Two"};
view.SetSomething(data);
data = new ViewData {Age = 3, Name = "Three"};
}
}
public class NotWorkingPresenter
{
private ViewData _data;
public void Init(IView view)
{
_data = new ViewData();
view.SetSomething(_data.Person(1, "One"));
view.SetSomething(_data.Person(2, "Two"));
_data.Person(3, "Three");
}
}
then my tests are ...
[Test]
public void GetDataOfLastCall()
{
ViewData dummydata=null;
var view = MockRepository.GenerateStub<IView>();
//Approach 1 : This works
var workingPresenter = new WorkingPresenter();
workingPresenter.Init(view);
var lastCall = view.GetArgumentsForCallsMadeOn(v => v.SetSomething(dummydata)).Count - 1;
var lastParams = view.GetArgumentsForCallsMadeOn(v => v.SetSomething(dummydata))[lastCall];
var lastData = (ViewData)lastParams[0];
//Approach 2: This doesn't
var notWorkingPresenter = new NotWorkingPresenter();
notWorkingPresenter.Init(view);
lastCall = view.GetArgumentsForCallsMadeOn(v => v.SetSomething(dummydata)).Count - 1;
lastParams = view.GetArgumentsForCallsMadeOn(v => v.SetSomething(dummydata))[lastCall];
lastData = (ViewData)lastParams[0];
What I want is to verify that the last call to SetSomething was with {name="Two", age=2}. Now workingPresenter does this but wouldn't you expect notWorkingPresenter to do so too?
There must be something else going on in your code (outside of the mocking). I just threw together a few items:
public interface IView
{
void SetSomething(ViewData data);
}
public class ViewData
{
public int Age { get; set; }
public string Name { get; set; }
}
And I tested it with:
[TestMethod]
public void GetDataOfLastCall()
{
var view = MockRepository.GenerateStub<IView>();
var data = new ViewData {Age = 1, Name = "One"};
view.SetSomething(data);
data = new ViewData { Age = 2, Name = "Two" };
view.SetSomething(data);
data = new ViewData { Age = 3, Name = "Three" };
var lastCall = view.GetArgumentsForCallsMadeOn(v => v.SetSomething(data)).Count - 1;
var lastParams = view.GetArgumentsForCallsMadeOn(v => v.SetSomething(data))[lastCall];
var lastData = (ViewData) lastParams[0];
}
And I got the values of 2 and "Two" inside the ViewData. It appears Rhino.Mocks supports what you want to do. Could you create a failing test case that shows the issue identified in your original question (where you got a reference to the most recent information)?