What causes if/else expressions to be re-evaluated? - c#

Working off the Blazor hosted template, in my MainLayout.cshtml, I have something like:
#inject UserInfo userInfo
#if(userInfo.Name == "bob")
{
<div class="login-panel">
<p>Hi Bob!</p>
</div>
}
else
{
<LoginPanel />
}
and then in a separate Login.cshtml:
#inject UserInfo userInfo
async void Login()
{
userInfo.Name = "bob";
}
But logging in doesn't cause a change to the 'login-panel' in MainLayout.cshtml. I was able to get this to work if everything is in a single file and I am only checking for whether a particular variable is null or not (see here), so I assume the Blazor framework has specific criteria which illicit a page to re-evaluate such blocks. Is this accurate? If so, what is the best way to force this re-evaluation?
Edit: I played around with it a bit more, eventually discovered the StateHasChanged protected member function, which I assume is called after Init has completed, hence why the previous example works. I think a working solution is to have my UserInfo object implement INotifyPropertyChanged, and then register a change handler inside MainLayout.cshtml to call StateHasChanged() for views that need to update when login status changes.

Yes. Blazor uses something called StateHasChanged just like INotifyPropertyChanged.
However the difference is, in XAML frameworks the XAML will only update those properties which has called INotifyPropertyChanged, but in Razor when StateHasChanged is called the UI framework will refresh the whole UI tree of the component (at least as of now) the element is residing.
Then who has called the StateHasChanged if its not done by you. When an element gets an input (You are clicking a button) the blazor will automatically set the StateHasChanged under the hood.
More reads"
https://github.com/aspnet/Blazor/issues/409
https://learn-blazor.com/pages/data-binding/
https://github.com/aspnet/Blazor/issues/359
https://github.com/aspnet/Blazor/issues/407

Related

Is it ok to use OnAfterRenderAsync() instead of OnParametersSetAsync() for database calls in Blazor Server?

I am reading that the recommendation is to use OnParametersSetAsync() for any async calls (such as database calls) inside a Blazor server component. The problem though is that OnParametersSetAsync() will fire twice in a default Blazor Server application, and I obviously don't want to access the database twice.
To avoid this problem, some people recommend replacing the "ServerPrerendered" render-mode in _Host.cshtml to "Server", which will avoid OnParametersSetAsync() firing twice. However, I don't like this solution, because the application will take longer to load for the user if we removing the initial static HTML phase.
So, my solution so far has been to put my database access calls inside OnAfterRenderAsync(), and to call StateHasChanged() once I am done. It looks something like this:
public partial class ExampleComponent
{
[Inject]
public IUserDataAccess UserAccess { get; set; }
[Parameter]
public string UserEmail { get; set; }
public IUser User { get; set; }
private bool _isLoading = true;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender == false)
return;
User = await UserAccess.GetOneAsync(UserEmail);
_isLoading = false;
StateHasChanged();
}
}
The database call gets made only once, because of the "if (firstRender == false)" condition.
This approach has been working well for me so far, but I suspect that there is something wrong with this way of proceeding, because the examples given online of a valid call to OnAfterRenderAsync() usually only mention JavaScript calls. I don't why that is though. Is there anything wrong with the example code I am giving above? And if this approach is not recommended for some reason, then how can we avoid the double call to the database if we use OnParametersSetAsync() (excluding the server-mode change discussed above)?
Thanks.
Everyone gets hung up on the initial double load and make huge design compromises that they don't need to. [Sorry, and please don't take this the wrong way]This is a probably a good example.
If you're using Blazor Server then, yes the SPA "Start page" gets rendered twice - once on the static first load and once when the SPA starts in the page.
But that's it. Everything after that only gets rendered once. If you are so hung up on that initial load, don't do "too much" in the landing page, make it "light".
To restate - This only happens once, every other routing component load in your SPA only happens once unless you force a re-load or hit F5.
On putting lots of stuff in OnAfterRender{Async}, in general DON'T. It's designed to handle after render activity such as JSInterop stuff. Doing things that then need a call to StateAndChanged is a bit self defeating!
All your once only Db stuff should be in OnInitialized{Async} - normally OnInitializedAsync as it will be async code. You have little control over when OnParametersSet{async} gets run: code in here gets called whenever the component is re-rendered.

Use injected service from JS code to static Blazor method

I have an app that uses a jQuery lib to show 'confirmation' dialogs.
When a user clicks 'OK' or 'Cancel' on the dialog, then first a callback is made to my Javascript code. From there I do a call to a Blazor method, based on the decision the user made.
This all looks something like this:
My js code:
$('.alert-yesno).on('click', function() {
// For simplicity I immediately call the Blazor method
DotNet.invokeMethodAsync('[Assembly name]', 'DoSomething')
.then(data => {
// do something...
});
});
Blazor page:
#inject MyService MyService
<button class="alert-yesno">Show dialog</button>
#code
{
[JSInvokable]
public static async Task DoSomething()
{
// How to use the non static MyService here...?
}
}
This all works fine. The Javascript part calls my Blazor method. But how can I use MyService from there? This is injected into the page. Which is good, because it makes use of dependency injection.
I don't want to create a new instance everytime from within the static method, because that way I loose all the auto injected dependencies.
How can I solve this issue?
See https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.0#instance-method-call for a more complete example, but you can pass a reference to the the .net instance to the javascript code by calling
DotNetObjectReference.Create(myInstance);
Then the javascript code can call the instance method on that object. Since the component (and thus the service which is injected into it) has a potentially different lifespan than the jquery click handler, this might cause some other issues, but the link above should be a good starting point.

MvvmCross show view before going further

I have an application where I can use popup views over the normal windows.
For getting the data the user needs to login so I have a token.
Has anyone experience with the way if there is no token, show first a view, then go further where you was heading to?
To make it more clear:
public void Init()
{
if (!CheckToken())
{
Task.Run(() => ShowViewModel<InsertPasswordViewModel>())
.ContinueWith(t => GetData());
}
else
{
//Do your thing
GetData();
}
}
The problem now is that the task is run so view is shown and immediately starts GetData but he has no token and crashes.
Any ideas or fixes are welcome
You shouldn't do navigation in Init. You are essentially navigating away from a page you are in the middle of navigating to. The navigation isn't finished executing when Init is called.
You are not getting a result from ShowViewModel either, so you can't rely on that returning anything when you are done with everything in InsertPasswordViewModel.
What you are probably looking for is navigating to your ViewModel with some kind of parameter telling where you came from. Then let InsertPasswordViewModel use that parameter to navigate back once password is OK.
You probably want to modify the View hierarchy too, which you can do through a custom presenter and a set of presentation hints.

Should My Code Behind Calls my View Model or My Service Directly?

I am trying to use MVVM with my windows phone 7 app. I am kinda confused on how I should do this.
I have a codebehind event for Navigating in a web browser. Now this event will eventually call a method that will log the user in using Google oAuth.
Now the code to call the login method to Google is in my service layer. I am not sure if I should make a property in my VM that calls my service layer or if I should just directly make a service layer object in my codebehind page and call it directly.
What is the best way to go?
Edit
Here is what I did
// code behind
private void wbGoogle_Navigating(object sender, NavigatingEventArgs e)
{
var queryParmas = e.Uri.ParseQueryString();
var code = queryParmas.ToList().Where(x => x.Key == "code").FirstOrDefault();
if (!code.Equals(new KeyValuePair<string, string>()))
{
loginVm.AuthenticateUser(code.Value, TestCallBack);
}
}
private void TestCallBack(Authorization a)
{
var d = a;
// maybe assign "a" to some property back in the VM.
}
// Vm
public void AuthenticateUser(string code, Action<GoogleContacts.Model.Authorization> CallbackOnSuccess)
{
loginService.SendCode(code, CallbackOnSuccess);
}
// service layer
public void SendCode(string code, Action<Authorization> CallbackOnSuccess)
{
var request = new RestRequest(TokenEndPoint, Method.POST);
request.AddParameter("code", code);
request.AddParameter("client_id", ClientId);
request.AddParameter("client_secret", SecretKey);
request.AddParameter("grant_type", GrantType);
request.AddParameter("redirect_uri", RedirectUrl);
client.ExecuteAsync<Authorization>(request, response =>
{
CallbackOnSuccess(response.Data);
});
}
The View Model is a model of your view - it is where all your logical work is actually done. The responsibility of the View is to present information to the UI and collect user input. Any other tasks performed by the View begins to tightly couple your code-base, and you end up with a maintenance nightmare.
To answer your question directly, your View (Code-Behind) should pass the fact that a UI element has been clicked on to a method (command binding or other mechanism) in your View Model.
Then, you Veiw Model decides what to do next. It may decide that it's time to navigate to a particular web-page, at which time it signals the view to move to the requested page.
The precise method upon which the ViewModel and View communicate has a lot to do with which framework you are using, and if you are a "ViewModel First" or a "View First" kinda guy.
To keep it simple... View Models are your business logic and brains of the app. The View should only deal with the display of data. I would consider authentication part of the "brains" and put that in your view model somewhere. You might even get a bit more sophisticated (depending on the needs of your app) and put the authentication Login method in a base class of your view model. This way the login call would be accessible via any page if need be.
Putting it in your code behind just tightly couples that code with your view - making it not very reusable. I would avoid that if you are going for the best architecture. Its ok to put stuff in your code behind of a view.
I would strongly disagree with the comment that MVVM exists to avoid code behind. The rule of thumb I use is this: Does the purpose of the View's codebehind exist to support view/display functions only? (perhaps you need to code to animate something or change a data template - thus changing its display). Code behind logic should be display specific. Thus, by this rule, a Login is not display specific and should not be a code behind method..
My 2 cents

JavaScript PageMethods call loosing HttpContext.Current State

I am in a bit tricky situation. I am using JavaScript's PageMethod functionality where I am invoking a PageMethod which works like a gem. However I am having an issue in accessing the HttpContext's state which returns me a value "SYSTEM" for
HttpContext.Current.User.Identity.Name
which is not the actual current User Name.
I know there are couple options like storing HttpContext.Current in a Session or saving Context's state in some other custom container but given a web farm environment I am assuming that this will not work as expected.
Here is the code I am working on with
function MyFunction(){
PageMethod.MyPageMethod();
}
here is the signature of the server method
[System.Web.Services.WebMethod()]
public static void MyPageMethod()
{
// gives me "SYSTEM"
var user = HttpContext.Current.User.Identity.Name;
}
Also if I use the above code to access user name in OnLoad event of the page then it works fine and returns me the CurrentUserName.
I am trying to get the above code to work in an ASP.NET Webform... :)
So I am wondering if there is a way to access the current actual user in page methods without making use of sessions.
Any help will be deeply appreciated.
NiK...
After quite some reading I think I was trying to do something which is not correct as to how page methods work. It gets quite tricky when your application's authentication system is windows based and these page methods when you invoke from JavaScript will not cause a postback and do not invoke the HttpModules. Instead it just calls that page method.
FYI, we had our own custom HTTPModule to handle security.This is even before any other HttpModule occurs and this was not being invoked while calling the page method as we are not doing a postback or even a partial postback (so the whole "niche" of a HTTPPost was missing). Moreover this led to a conclusion that we were making service calls without any authentication and was potentially a big security issue for us.
The bottom line is it was a bad design, well having said that I would like to mention about the solution/workaround we came up with and here is what we did. So, the only option we had is to do a postback keeping the UI alive and we wanted to update a label's message asynchronously and we achieved it by doing a hack using Sys.Application.add_init.
<script language="javascript" type="text/javascript" >
Sys.Application.add_init(function() {
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
});
function beginProcess() {
processCurrentItem();
}
var currentItem = 0;
function processCurrentItem() {
if (currentItem < 5) {
currentItem = currentItem + 1;
__doPostBack('updatePanel', currentItem);
}
}
function endRequest() {
processCurrentItem();
}
</script>
The markup we had in place was pretty simple with a label in the update panel and a button that invokes the "beginProcess()" function. Finally in the OnLoad we had the following code in place
protected override void OnLoad(EventArgs e)
{
if (this.IsPostBack)
{
this.lblLabel.Text = "text you may wanna update with";
// Call the Method you may wanna call
// also you may use Request["__EVENTARGUMENT"] to know who caused the
// postback and Request["__EVENTTARGET"] to access the argument you may
// have passed in.
}
}
And this solution is no longer using the JavaScript Page methods. And based on this solution if anyone thinks I am missing something here or think there is any other other way of doing this then do update this post with your suggestions.
NiK

Categories

Resources