Adding Ratings and Saving YouTube Study Cards to SharePoint Online


Part 2 — Adding Ratings and Saving YouTube Study Cards to SharePoint Online (WPF + MVVM + Microsoft Graph)

Goal

Extend the WPF YouTube Cards app so you can:

  • rate a video (0–10 or stars)
  • add notes and a study status
  • save/update that record in SharePoint Online
  • later filter your personal study library (“Favorites”, “To Watch”, etc.)

This turns your YouTube search UI into a personal learning database.


Why Microsoft Graph for SharePoint Lists

Microsoft Graph provides a modern, consistent API for SharePoint lists:

  • Create list items
  • Update existing items
  • Query/filter items
  • Works with OAuth (Entra ID) and standard enterprise auth patterns

This is the cleanest long-term route for a “study app” that stores structured data in SharePoint.


Data Model

UI Model (Card) additions

Add the following fields to your VideoCard:

  • Rating (int 0–10)
  • Status (string: ToWatch / Watching / Watched / Favorite)
  • Notes (string)
  • SavedToSharePoint (bool)
  • optional: LastSavedAt

These fields live in the card so you can edit and save per video.


SharePoint List Schema (Example)

Create a SharePoint list (placeholder names):

Site: https://contoso.sharepoint.com/sites/LearningHub
List title: Learning Videos

Columns:

  • Title (Single line text) → store video title
  • VideoId (Single line text) → unique key
  • VideoUrl (Hyperlink)
  • Channel (Single line text)
  • ThumbnailUrl (Single line text)
  • SearchQuery (Single line text)
  • Rating (Number)
  • Status (Choice: ToWatch, Watching, Watched, Favorite)
  • Notes (Multiple lines text)

Important: keep VideoId indexed (performance) and treat it as your “natural key”.


Authentication (Public-Safe Guidance)

For a desktop WPF app, the recommended flow is:

  • Register an app in Entra ID (Azure AD)
  • Use MSAL (Microsoft.Identity.Client)
  • Acquire a token for Microsoft Graph

You’ll need Graph permissions for SharePoint list operations. In a real environment, you choose the minimal permission model your admin allows.

Public note: never embed real IDs/secrets. Use placeholders and environment config.


Graph Endpoints You’ll Use

You have two main strategies to target a list:

Strategy A — Use SiteId and ListId (fast and stable)

  • Resolve siteId once
  • Resolve listId once
  • Then call list item endpoints

Strategy B — Use Site path addressing (readable)

Graph supports site path patterns, but for list operations, you often end up using IDs anyway.


App Architecture Update

Add a “SharePoint layer”:

  • SharePointVideoRepository
    • FindByVideoIdAsync(videoId)
    • CreateAsync(videoCard)
    • UpdateAsync(itemId, videoCard)
    • UpsertAsync(videoCard)

Your ViewModel calls this repository when a user clicks “Save”.

This keeps MVVM clean.


UI Changes (WPF)

In each card, add:

  • rating dropdown (0–10) OR a slider
  • status dropdown
  • notes TextBox (small)
  • Save button

Example UI concept:

  • Card footer: Open | Save
  • Under title: rating/status controls
  • Description stays read-only

Implementation: Core Building Blocks (Code)

1) Update the VideoCard model

public int Rating { get; set; } // 0..10
public string Status { get; set; } // ToWatch / Watching / Watched / Favorite
public string Notes { get; set; }
public bool SavedToSharePoint { get; set; }

2) Create an Auth provider with MSAL (Graph token)

// PSEUDO / TEMPLATE (placeholders)
var app = PublicClientApplicationBuilder
.Create("YOUR_CLIENT_ID")
.WithAuthority("https://login.microsoftonline.com/YOUR_TENANT_ID")
.WithRedirectUri("http://localhost")
.Build();
string[] scopes = new[] { "https://graph.microsoft.com/.default" };
// or delegated scopes like: "Sites.ReadWrite.All" depending on your model
var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
var accessToken = result.AccessToken;

You then attach that token to HTTP requests to Graph.


3) Create a minimal Graph client wrapper

public sealed class GraphClient
{
private readonly Func<Task<string>> _getToken;
public GraphClient(Func<Task<string>> getToken)
{
_getToken = getToken;
}
public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage req)
{
var token = await _getToken();
req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
using (var http = new HttpClient())
{
return await http.SendAsync(req);
}
}
}

4) Repository: Upsert logic

Pseudo-steps:

  1. Query list items filtered by VideoId
  2. If found → PATCH
  3. Else → POST create

Find by VideoId

GET /sites/{siteId}/lists/{listId}/items?expand=fields&$filter=fields/VideoId eq '{videoId}'

Create item

POST /sites/{siteId}/lists/{listId}/items
{
"fields": {
"Title": "...",
"VideoId": "...",
"VideoUrl": "...",
"Channel": "...",
"ThumbnailUrl": "...",
"SearchQuery": "...",
"Rating": 8,
"Status": "Watched",
"Notes": "..."
}
}

Update item

PATCH /sites/{siteId}/lists/{listId}/items/{itemId}/fields
{
"Rating": 9,
"Status": "Favorite",
"Notes": "Updated notes..."
}

MVVM Wiring: “Save” Command Per Card

You can do this cleanly by making VideoCard carry commands, or you keep the command in the parent VM and pass the card as parameter.

Example:

  • SaveVideoCommand in MainViewModel
  • CommandParameter="{Binding}"

Then inside VM:

private async Task SaveVideoAsync(VideoCard card)
{
await _repo.UpsertAsync(card);
card.SavedToSharePoint = true;
}

Common Pitfalls and Fixes

1) Cross-thread exceptions (WPF)

Any update to ObservableCollection or UI-bound properties must happen on UI thread.

✅ Fix: keep await without ConfigureAwait(false) inside UI code, or marshal to Dispatcher.

2) Duplicates in SharePoint

If you don’t filter by VideoId, you’ll create duplicates.

✅ Fix: always upsert by VideoId.

3) SharePoint column internal names

SharePoint field internal name may differ from display name.

✅ Fix:

  • create columns carefully
  • verify internal names in list settings or via API once
  • use internal names in Graph payload.

Edvaldo Guimrães Filho Avatar

Published by