Resolved MVVM - persistent API key between viewmodels

ConsKa

Well-known member
Joined
Dec 11, 2020
Messages
140
Programming Experience
Beginner
I have an application that is hard coded with an API key....it works, perfectly fine.

I created a login screen so other people can use it with their API key.

It will not hold the API key, I have tried assigning it to a public property, I have tried creating a class to hold it in a property and then call to that class to get it, I created a textbox that was hidden and tried to write it to the hidden textbox....

I run the program, the first run through it works - the api key is there, it has been properly passed, but the moment it calls another method (the get method calls a datagrid view which is populated in another method by making selections form the json objects)....the API key is null and the json is null - hard coded - no problem. I don't understand why it goes to null, and I don't understand why the json goes to null - they are both properties which is how the different methods are able to populate the datagrid view without keep calling to the API.

I have seen solutions online, that require like 4 pages of code (exaggeration for effect) - and I just feel like....it can't be that hard can it? to simply pass a passkey to another viewmodel.

I am using Caliburn Micro for the MVVM model, dependency injection stuff - if that helps.

Thanks for any help offered, because this one has me stumped, I mentioned 3 things above - but I have tried like 15 stupid ideas that I am too embarrassed to post....
 
With just your vague description, it almost feels like you are creating new instances of everything each time. Please show us your code, or some minimal code that reproduces the problem.

A ViewModel is just an adapter for the View to be able to talk to the ViewModel. Put the API key in the Model. Pass the same instance of the Model to the two different ViewModels. Your DI framework should be able to help you with this.

Also there is nothing that says that you have to use two different instances of a ViewModel for two different Views. You can just use the same instance of the ViewModel and it to two different Views. Again, your DI framework should be able to help you with this otherwise it is not worth its salt.
 
Ok sorry for delay, this site is blocked at work - I have applied for them to unblock it, but it is a large organisation....

This is a bit of a code dump so apologies, I have cut out all of the logic for checking Json objects (which is a lot of code) but still 100 lines in the main page.

I have tried passing the API to the StartApi() method, to a separate Model with a property, to the GetDesk() method itself, I have tried making it a property in various places.

It just doesn't want to stick, the same for the JSON, the moment I undo the hard coding of the API key, json goes to null as soon as it leaves GetDesk() - and that I don't understand at all....why would changing one line essentially make it so that the json goes to null - I could get my head around the idea that the API key itself isn't persistent....but the json - it is persistent when the key is, but it isn't reliant on the key after being called?

I have looked through the 750 lines of code and the API itself is called once....in GetDesk()....at no other point am I relying on it, none of the other code relies on it....though it does rely on the json.

And yes, I know I am failing at the MVVM - it isn't a lack of belief or knowledge of the system, it is an application built piecemeal with no time to actually sit down and parse it all out to models...blame the pressure of a busy work environment where this is simply not my job - this is kind of a hobby - this is the first time the code has left that environment so I might take the time at home to parse it all out to models....if I can get my head around this json problem.

This is the login page - it basically takes an API key into the tbApiKeyCheck textbox - and then checks it against the API - if successful send an event message and loads the UnassignedViewModel.

Login Page:
namespace DataGridTestApp.ViewModels
{
    public class LoginViewModel : Screen
    {
        private IEventAggregator _events;

        private string _tbAPIKeyCheck;
        public string tbAPIKeyCheck
        {
            get { return _tbAPIKeyCheck; }
            set
            {
                _tbAPIKeyCheck = value;
                NotifyOfPropertyChange(() => tbAPIKeyCheck);
            }
        }
        public LoginViewModel()
        {

        }
        public LoginViewModel(IEventAggregator events)
        {
            _events = events;
        }
        public async void btnApi()
        {
            if (tbAPIKeyCheck == "" || tbAPIKeyCheck == null)
            {
                MessageBox.Show("You must enter an API Key");
                return;
            }
            else
            {
                string test = "APICALLSTRING";
                var api = tbAPIKeyCheck;
                string url = URL;

                string shortUrl = url + test;

                HttpClient httpClient = new HttpClient();
                HttpRequestMessage request = new HttpRequestMessage();
                request.RequestUri = new Uri(shortUrl);
                request.Method = HttpMethod.Get;
                request.Headers.Add("APIWEBKEY", api);
                HttpResponseMessage response = await httpClient.SendAsync(request);

                if (!response.IsSuccessStatusCode)
                {
                    MessageBox.Show("You must enter a Valid API Key");
                    return;
                }
                else
                {
                    _events.PublishOnUIThread(new LoginEvent());
                    UnassignedViewModel uvm = new UnassignedViewModel();
                    
                    uvm.StartAPI();
                }
                httpClient.Dispose();
            }
        }}
    }
}

This is the main view page:

Main Page:
namespace DataGridTestApp.ViewModels
{
    public class UnassignedViewModel : Screen
    {
        #region Public Properties
        DispatcherTimer aTimer = new DispatcherTimer();
        // various screen properties
        string api;
        private BindableCollection<TicketModel> _ticket;

        public BindableCollection<TicketModel> Ticket
        {
            get { return _ticket; }
            set
            {
                _ticket = value;
                NotifyOfPropertyChange(() => Ticket);
            }
        }
        #endregion

        string json;
        Rootobject token;
        DataTable table;
        
        public UnassignedViewModel()
        {
            SQLCall sql = new SQLCall();

            table = sql.SQLData();
            aTimerSetup();
            
        }
        private void aTimerSetup()
        {
            aTimer.Tick += aTimer_Tick;
            aTimer.Interval = new TimeSpan(0, 0, 120);
            aTimer.Start();
        }
        public void aTimer_Tick(object sender, EventArgs e)
        {
            GetDesk();
        }
        public async void GetDesk()
        {
            api = "HardCodedApiKey";

            string test = "APISTRING";
            string url = URL;

            string shortUrl = url + test;

            HttpClient httpClient = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage();
            request.RequestUri = new Uri(shortUrl);
            request.Method = HttpMethod.Get;
            request.Headers.Add("APIWEBKEY", api);
            HttpResponseMessage response = await httpClient.SendAsync(request);

            json = await response.Content.ReadAsStringAsync();

            token = JsonConvert.DeserializeObject<Rootobject>(json);

            StringBuilder sb = new StringBuilder();

            foreach (var item in token.requests)
            {
                // code that goes through each item in the objects
                // manipulates them checking for particular keywords
            }

            // Collection for populating a datagrid view (binding in xaml)
            Ticket = new BindableCollection<TicketModel>();

            foreach (var item in token.requests)
            {
                // more of the above
            }
            httpClient.Dispose();
            btnAll();
        }
        public void StartAPI()
        {
            aTimerSetup();
            GetDesk();
        }
        public void btnHighPriority()
        {
            Ticket = new BindableCollection<TicketModel>();
            
            foreach (var item in token.requests)
            {
                // code that does more looking and sends to a
                // datagrid via the BindableCollection<TicketModel>
            }
        }
        public void btnLastUpdated()
        {
            // Same as high proprity
        }
        public void btnDailies()
        {
            // Same as above
        }

        public void btnRefresh()
        {
            // clears the datagrid and calls GetDesk()
        }
        public void btnAll()
        {
            // an unfiltered view.
        }

        public class TicketModel
        {
            // Model for populating the datagrid.
        }
        public void btnHandOff()
        {
            // Same as priority etc.
        }
    }
}

If anyone has any idea why the json would go null or how I can keep the apikey in the code like it is when hardcoded - would be appreciated.
 
I kinda agree with Skydiver. I was also trying to explain this the other day on another topic and got my head chewed off. If you have two different views, then you can have two different models but not two different view models.
Quote from : Wintellect - I didn't read the code within the link, I was just reading over what he was saying about this observer pattern.
The Chicken or the Egg?

You might have heard discussion about view first or viewmodel first. In general, I believe most developers agree that a view should have exactly one viewmodel. There is no need to attach multiple viewmodels to a single view. If you think about separation of concerns, this makes sense, because if you have a “contact widget” on the screen bound to a “contact viewmodel” and a “company widget” bound to a “company viewmodel”, these should be separate views, not a single view with two viewmodels.
I liked the comments the most. It really hits on what I have been saying about this pattern recently.

I was the one who pushed this pattern on people when I joined this board, and now I kinda wish I didn't. The more I use it, the more I dislike it. It has it's perks, but it also adds complications too for certain apps. It was designed for use with complex binding rules using multiple models. And as I keep saying recently on these wpf topics. WPF does not require you to use MVVM. That's just microsoft plugging the pattern they wanted us to use, which they are now telling people what I have been saying. That the observer pattern should only be used in complex situations where there are multiple models involved, and much to my amusement, the guy who developed it (John Gossman), also says its often overkill to use in simple applications according to the comments on that link I shared. As the developer of your application, you need to decide the pattern for your application, and that pattern will be and should be dictated by your applications requirements.

Regarding what you are explaining above, it sounds like you have a scoping issue, or you are indeed accessing the property after you called new on your model, you are not accessing the same instance. Can I ask what source of documentation are you following to implement this pattern?
 
It would also help to see the Screen Class you have, and your xaml.

When I have more time, I will come back and look at this for you if no one else does.
 
Regarding what you are explaining above, it sounds like you have a scoping issue, or you are indeed accessing the property after you called new on your model, you are not accessing the same instance. Can I ask what source of documentation are you following to implement this pattern?

The base model comes from a Tim Corey video series - for a far more complex application, the Retail Management series - where MVVM is probably useful, but I liked the clean way it worked and the way the UI presents - it was also a pretty good introduction to dependency injection and the whole MVVM idea applied.

Where do you set your datacontext?

This is using Caliburn.Micro....you do not set a datacontext. The View and Viewmodel are linked by the

C#:
public class LoginViewModel : Screen

which Caliburn micro interprets and essentially does the background plugging via Bootstrapper so that your properties are linked to the appropriate view.

Which is one of the reason I am not hot on Skydiver's idea of having one ViewModel for two views, it would require redoing the application from the base up and not using Caliburn Micro - which I can do - but I don't think that is fundamentally the problem.

The thing that is throwing me is that through breakpoints, I can see the API is there, I can see the json is there, but when it gets to the end of GetDesk() and goes to btnAll() - which is just a filter to the DataGrid that is bound in XAML to the TicketModel - the json is now null.

That I don't understand at all - why would taking away the hardcoded API and replacing it with a passed variable all of a sudden make the json null when it leaves GetDesk()?
 
The changes I made to make the json null was simply this:

At the bottom of the code for the login page:

C#:
uvm.StartAPI(api);

In the mainview:

C#:
public void StartAPI(string inputApi)
        {
            api = inputApi;
            aTimerSetup();
            GetDesk();
        }

Then at the start of GetDesk() I simply comment out the line with the hardcoded API Key.

api is a public property, and this works, it calls to the API, I get a success code, I get the json - all the work in GetDesk() works perfectly, gets to the end, goes to btnAll() - the json is gone, it is null....and so is the api property which now reflects a null reference.
 
Ahh, I see.

I have only heard of it, and not actually used it.

I prefer to write my own classes. This complicates how much help you may receive here, unless one of the other devs have experience using this.
This topic technically becomes a third party topic now since you are using a library and not following the default documentation outlined by microsoft.
 
Anyway, I don't have time to get into this as I am tied up with my own wpf app.

I will need a few days to skim over your replies above.

Hopefully the other guys can help you. But I would have thought you would have followed the documentation they appear to have listed on their site rather than a Youtube video... When a library version changes, and at the time it was recorded, it may not follow new changes and updates made to the library after that video was made, making it easy for mistakes to be made by copying from the video. Browse over the documentation on the devs website. You might find something you missed.
 
Ahh, I see.

I have only heard of it, and not actually used it.

I prefer to write my own classes. This complicates how much help you may receive here, unless one of the other devs have experience using this.
This topic technically becomes a third party topic now since you are using a library and not following the default documentation outlined by microsoft.

Thing is, I don't think this has anything to do with Caliburn.Micro.

It doesn't interact with the API key. Once we are in the ViewModel - Caliburn is done - it doesn't play a role in the calling to the API, or the creation of the json it is merely a connection between the view and viewmodel.
 
Sorry I don't have more time to spend on this. There's a bit too much code for the time I have free right now. But I hope you find the culprit. :cool:

Look over your code. Something is resetting it. The debugger is your best way of identifying the issue.

I'm out
 
I don't think line 73 should be there at a quick glance?

Good spot.

I just broke properly now so it won't work even with a hard coded API key.

So I think I will just rebuild it from scratch and take the view that Skydiver expressed at the start - don't need two views and two viewmodels, it isn't that complex.

I think I will just chuck it in a single View/ViewModel more hours of my life :D
 
@Sheepings : As I recall, you said that you did Ruby on Rails for a while. It was RoR which made coding by convention popular. Caliburn.Micro follows suit with the same coding by convention. Unless specifically overridden, it will use convention to figure out which objects are related to each other and which should be injected where.
 
Back
Top Bottom