Congratulations!

[Valid RSS] This is a valid RSS feed.

Recommendations

This feed is valid, but interoperability with the widest range of feed readers could be improved by implementing the following recommendations.

Source: http://feeds.feedburner.com/WaldekMastykarz

  1. <?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:webfeeds="http://webfeeds.org/rss/1.0"><channel><title>Waldek Mastykarz</title><description>Innovation matters</description><link>https://blog.mastykarz.nl/</link><image><url>https://blog.mastykarz.nl/favicon.ico</url><title>Waldek Mastykarz</title><link>https://blog.mastykarz.nl/</link></image><atom:link href="https://blog.mastykarz.nl/feed.xml" rel="self" type="application/rss+xml"/><pubDate>Sun, 04 Feb 2024 11:33:54 GMT</pubDate><lastBuildDate>Sun, 04 Feb 2024 11:33:54 GMT</lastBuildDate><webfeeds:analytics id="UA-3652888-1" engine="GoogleAnalytics"/><ttl>60</ttl><item><title>Add background color to menu bar icon in macOS</title><link>https://blog.mastykarz.nl/add-background-color-menu-bar-icon-macos/</link><guid isPermaLink="true">https://blog.mastykarz.nl/add-background-color-menu-bar-icon-macos/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-on.png&quot; alt=&quot;Add background color to menu bar icon in macOS&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;When building apps for macOS, you might want to add a background color to your icon in the menu bar. This is a great way to make your app stand out and make it easier for users to find your app in the menu bar.&lt;/p&gt;
  2. &lt;p&gt;To add a background color to your icon in the menu bar, the trick is to find the right view to apply the background color to. Here&apos;s how you can do it in Swift.&lt;/p&gt;
  3. &lt;p&gt;Start, by getting a reference to the status item. Then, get the status item&apos;s button. Configure the image to fit the button.&lt;/p&gt;
  4. &lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// get a reference to the status item
  5. let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
  6. // get the status item&apos;s button
  7. let button = statusItem.button
  8. // configure the image to fit the button
  9. button?.imageScaling = NSImageScaling.scaleProportionallyUpOrDown
  10. &lt;/code&gt;&lt;/pre&gt;
  11. &lt;p&gt;Next, get the view behind the button and apply the background color to it. Set the image on the button. To align the button with standard macOS buttons such as microphone, camera or screen recording, configure the corners to be rounded.&lt;/p&gt;
  12. &lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;// this is the view you need to get
  13. let buttonView = button?.superview?.window?.contentView
  14. // set the image on the button
  15. button.image = NSImage(named:NSImage.Name(&amp;quot;ProxyEnabledWhite&amp;quot;))
  16. // set the background color
  17. buttonView.layer?.backgroundColor = NSColor.systemOrange.cgColor
  18. // round the corners
  19. buttonView.layer?.cornerRadius = 4
  20. &lt;/code&gt;&lt;/pre&gt;
  21. &lt;p&gt;If you get the wrong view, the background color will overlay the image. Also, configure the &lt;code&gt;template-rendering-intent&lt;/code&gt; of the image to show on the button as &lt;code&gt;original&lt;/code&gt;. This will ensure that the image is not affected by the background color and has enough contrast.&lt;/p&gt;
  22. &lt;p&gt;Here&apos;s the result:&lt;/p&gt;
  23. &lt;p&gt;&lt;picture&gt;
  24.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-on.webp&quot; type=&quot;image/webp&quot;&gt;
  25.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-on.png&quot; alt=&quot;ProxyStat showing an orange icon when system proxy is configured&quot;&gt;
  26. &lt;/picture&gt;&lt;/p&gt;
  27. &lt;p&gt;To clear the background color, change the &lt;code&gt;backgroundColor&lt;/code&gt; to &lt;code&gt;NSColor.clear&lt;/code&gt;. Change the image configure as a template image so that it automatically adjusts to the system appearance. If you need, you can also choose to display the image as disabled.&lt;/p&gt;
  28. &lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;buttonView.layer?.backgroundColor = NSColor.clear.cgColor
  29. button.image = NSImage(named:NSImage.Name(&amp;quot;ProxyEnabledBlack&amp;quot;))
  30. button.appearsDisabled = true
  31. &lt;/code&gt;&lt;/pre&gt;
  32. &lt;p&gt;Here&apos;s the result:&lt;/p&gt;
  33. &lt;p&gt;&lt;picture&gt;
  34.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-off.webp&quot; type=&quot;image/webp&quot;&gt;
  35.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-off.png&quot; alt=&quot;ProxyStat showing a greyed out icon when system proxy is not configured&quot;&gt;
  36. &lt;/picture&gt;&lt;/p&gt;
  37. &lt;h2&gt;Summary&lt;/h2&gt;
  38. &lt;p&gt;Adding a background color to your icon in the menu bar on macOS is a great way to make your app stand out and make it easier for users to find your app in the menu bar. By applying the background color to the right view, you can combine it with your icon and align it with standard macOS buttons such as microphone, camera or screen recording.&lt;/p&gt;
  39. </description><pubDate>Sun, 04 Feb 2024 11:33:54 GMT</pubDate></item><item><title>Easily see if you have system proxy configured on macOS</title><link>https://blog.mastykarz.nl/easily-see-system-proxy-configured-macos/</link><guid isPermaLink="true">https://blog.mastykarz.nl/easily-see-system-proxy-configured-macos/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-on.png&quot; alt=&quot;Easily see if you have system proxy configured on macOS&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;ProxyStat is a macOS utility that shows when you have a system proxy configured. When you enable a system proxy, the ProxyStat icon the menu bar turns orange.&lt;/p&gt;
  40. &lt;p&gt;&lt;picture&gt;
  41.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-on.webp&quot; type=&quot;image/webp&quot;&gt;
  42.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-on.png&quot; alt=&quot;ProxyStat showing an orange icon when system proxy is configured&quot;&gt;
  43. &lt;/picture&gt;&lt;/p&gt;
  44. &lt;p&gt;When you don&apos;t have a system proxy configured, the icon is greyed out.&lt;/p&gt;
  45. &lt;p&gt;&lt;picture&gt;
  46.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-off.webp&quot; type=&quot;image/webp&quot;&gt;
  47.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2024/02/proxystat-proxy-off.png&quot; alt=&quot;ProxyStat showing a greyed out icon when system proxy is not configured&quot;&gt;
  48. &lt;/picture&gt;&lt;/p&gt;
  49. &lt;p&gt;When using tools like &lt;a href=&quot;https://aka.ms/devproxy&quot;&gt;Dev Proxy&lt;/a&gt;, Charles or Proxyman, it&apos;s easy to forget to turn off them off in your system proxy configuration. It can be frustrating to understand why your internet is not working and you can&apos;t open any website. ProxyStat is a great way to quickly see if you have a system proxy configured.&lt;/p&gt;
  50. &lt;p&gt;&lt;a href=&quot;https://mastykarzblog.blob.core.windows.net/downloads/ProxyStat.pkg&quot;&gt;Download ProxyStat&lt;/a&gt; for macOS 14.2 and configure it to automatically launch on startup.&lt;/p&gt;
  51. </description><pubDate>Sun, 04 Feb 2024 11:11:05 GMT</pubDate></item><item><title>Easily debug Microsoft Graph Java SDK requests</title><link>https://blog.mastykarz.nl/easily-debug-microsoft-graph-java-sdk-requests/</link><guid isPermaLink="true">https://blog.mastykarz.nl/easily-debug-microsoft-graph-java-sdk-requests/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/11/banner-graph-client-java-debug.png&quot; alt=&quot;Easily debug Microsoft Graph Java SDK requests&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;Using the Microsoft Graph Java SDK is a convenient way to connect your app to Microsoft 365. Here&apos;s how you can easily debug API requests issued from the SDK.&lt;/p&gt;
  52. &lt;h2&gt;Build apps that work with Microsoft 365&lt;/h2&gt;
  53. &lt;p&gt;Microsoft 365 is a platform where millions of users work together every day. You can create apps that use the data and insights from Microsoft 365 to help them do their work more efficiently and effectively.&lt;/p&gt;
  54. &lt;p&gt;Microsoft Graph is the API that connects you to your organization&apos;s data in Microsoft 365. You can access it using its REST endpoints. If you build an app connected to Microsoft 365 using Java, you should consider using the Microsoft Graph Java SDK. The SDK makes authentication easier and handles complex scenarios like backing off when throttled, following redirects and uploading large files, which means that you can focus on building your app instead of its plumbing!&lt;/p&gt;
  55. &lt;h2&gt;Use the Microsoft Graph Java SDK to connect your Java app to Microsoft 365&lt;/h2&gt;
  56. &lt;p&gt;To begin using the Microsoft Graph Java SDK, create a Graph client with an authentication provider. Then, you can start calling Microsoft Graph to access and manipulate data in Microsoft 365.&lt;/p&gt;
  57. &lt;p&gt;For building a deamon app, you&apos;d use code similar to following:&lt;/p&gt;
  58. &lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.azure.identity.ClientSecretCredential;
  59. import com.azure.identity.ClientSecretCredentialBuilder;
  60. import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
  61. import com.microsoft.graph.requests.GraphServiceClient;
  62.  
  63. final ClientSecretCredential credential = new ClientSecretCredentialBuilder()
  64.    .clientId(clientId)
  65.    .clientSecret(clientSecret)
  66.    .tenantId(tenantId)
  67.    .build();
  68. final TokenCredentialAuthProvider authProvider = new TokenCredentialAuthProvider(credential);
  69. final GraphServiceClient&amp;lt;okhttp3.Request&amp;gt; graphClient = GraphServiceClient.builder()
  70.    .authenticationProvider(authProvider)
  71.    .buildClient();
  72. &lt;/code&gt;&lt;/pre&gt;
  73. &lt;h2&gt;Debug Microsoft Graph Java SDK API requests&lt;/h2&gt;
  74. &lt;p&gt;When you start building your app, it can happen that you&apos;ll get an error calling Microsoft Graph. Looking at the returned error information should give you a clearer idea of what&apos;s wrong and how to fix it.&lt;/p&gt;
  75. &lt;p&gt;If that&apos;s not the case though, and you need to see the request and response that goes over the wire, here&apos;s a trick that you can use.&lt;/p&gt;
  76. &lt;h3&gt;Microsoft Graph Java SDK middleware&lt;/h3&gt;
  77. &lt;p&gt;The Microsoft Graph Java SDK supports the concept of &lt;a href=&quot;https://learn.microsoft.com/graph/sdks/customize-client?tabs=java&quot;&gt;middleware&lt;/a&gt;: functions that run as part of its request- and response pipeline and have access to the information about the outgoing request and received response. If you want to inspect API requests and responses issued by the Microsoft Graph Java SDK, building a custom middleware is the easiest way about it. Middleware has access to the full request context information which includes information about the outgoing request and received response.&lt;/p&gt;
  78. &lt;h3&gt;Debug middleware for Microsoft Graph Java SDK&lt;/h3&gt;
  79. &lt;p&gt;Start, with creating a new file named &lt;code&gt;DebugHandler.java&lt;/code&gt;. Add the following code:&lt;/p&gt;
  80. &lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;package connector;
  81.  
  82. import java.io.IOException;
  83. import java.nio.charset.StandardCharsets;
  84.  
  85. import okhttp3.Response;
  86. import okio.Buffer;
  87.  
  88. public class DebugHandler implements okhttp3.Interceptor {
  89.  @Override
  90.  public Response intercept(Chain chain) throws IOException {
  91.    System.out.println(&amp;quot;&amp;quot;);
  92.    System.out.printf(&amp;quot;Request: %s %s%n&amp;quot;, chain.request().method(), chain.request().url().toString());
  93.    System.out.println(&amp;quot;Request headers:&amp;quot;);
  94.    chain.request().headers().toMultimap()
  95.        .forEach((k, v) -&amp;gt; System.out.printf(&amp;quot;%s: %s%n&amp;quot;, k, String.join(&amp;quot;, &amp;quot;, v)));
  96.    if (chain.request().body() != null) {
  97.      System.out.println(&amp;quot;Request body:&amp;quot;);
  98.      final Buffer buffer = new Buffer();
  99.      chain.request().body().writeTo(buffer);
  100.      System.out.println(buffer.readString(StandardCharsets.UTF_8));
  101.    }
  102.  
  103.    final Response response = chain.proceed(chain.request());
  104.  
  105.    System.out.println(&amp;quot;&amp;quot;);
  106.    System.out.printf(&amp;quot;Response: %s%n&amp;quot;, response.code());
  107.    System.out.println(&amp;quot;Response headers:&amp;quot;);
  108.    response.headers().toMultimap()
  109.        .forEach((k, v) -&amp;gt; System.out.printf(&amp;quot;%s: %s%n&amp;quot;, k, String.join(&amp;quot;, &amp;quot;, v)));
  110.    if (response.body() != null) {
  111.      System.out.println(&amp;quot;Response body:&amp;quot;);
  112.      System.out.println(response.peekBody(Long.MAX_VALUE).string());
  113.    }
  114.  
  115.    return response;
  116.  }
  117. }
  118. &lt;/code&gt;&lt;/pre&gt;
  119. &lt;p&gt;You start with defining a new handler that inherits from the okhttp3.Interceptor class which defines middleware handlers for the Microsoft Graph Java SDK. Next, you overrde the &lt;code&gt;intercept&lt;/code&gt; method, that is called on each API request handled by the SDK.&lt;/p&gt;
  120. &lt;p&gt;When this function runs as part of the client&apos;s middleware, it will first log information about the outgoing request. Then, it will call the next middleware in chain and wait for its execution. This is where the actual API request will be executed. Finally, it will print response result, its headers, and if suitable, the response body.&lt;/p&gt;
  121. &lt;h3&gt;Use the debug middleware&lt;/h3&gt;
  122. &lt;p&gt;The last step is to add the debug middleware to the Graph client. To do that, update the client&apos;s initiation code as follows:&lt;/p&gt;
  123. &lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.azure.identity.ClientSecretCredential;
  124. import com.azure.identity.ClientSecretCredentialBuilder;
  125. import com.microsoft.graph.authentication.TokenCredentialAuthProvider;
  126. import com.microsoft.graph.httpcore.HttpClients;
  127. import com.microsoft.graph.requests.GraphServiceClient;
  128. import okhttp3.OkHttpClient;
  129.  
  130. final ClientSecretCredential credential = new ClientSecretCredentialBuilder()
  131.    .clientId(clientId)
  132.    .clientSecret(clientSecret)
  133.    .tenantId(tenantId)
  134.    .build();
  135. final TokenCredentialAuthProvider authProvider = new TokenCredentialAuthProvider(credential);
  136. final OkHttpClient okHttpClient = HttpClients.createDefault(authProvider)
  137.    .newBuilder()
  138.    .addInterceptor(new DebugHandler())
  139.    .build();
  140. final GraphServiceClient&amp;lt;okhttp3.Request&amp;gt; graphClient = GraphServiceClient.builder()
  141.    .httpClient(okHttpClient)
  142.    .buildClient();
  143. &lt;/code&gt;&lt;/pre&gt;
  144. &lt;p&gt;We changed the Graph client instantiation code, to get the default HTTP client from the Microsoft Graph Java SDK with its default interceptors. For debugging to have access to all request and response information, we need to add it as the last interceptor.&lt;/p&gt;
  145. &lt;p&gt;With this setup in place, when you call Microsoft Graph using the client, you&apos;ll see the following output in the console:&lt;/p&gt;
  146. &lt;blockquote&gt;
  147. &lt;p&gt;TIP: To see this middleware in action, check out the sample &lt;a href=&quot;https://adoption.microsoft.com/en-us/sample-solution-gallery/sample/pnp-graph-connector-java-markdown/&quot;&gt;Microsoft Graph connector&lt;/a&gt;.&lt;/p&gt;
  148. &lt;/blockquote&gt;
  149. &lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;$ ./gradlew run  
  150.  
  151. &amp;gt; Task :app:run
  152.  
  153. Request: GET https://graph.microsoft.com/v1.0/users?%24top=1
  154. Request headers:
  155. accept: */*
  156. authorization: Bearer eyJ0eXAiOiJKV1...
  157. client-request-id: a8cd2ffa-e896-43b6-84b2-6a940552e067
  158. sdkversion: graph-java/v5.75.0, graph-java-core/v2.0.19 (featureUsage=0), java/17.0.8
  159.  
  160. Response: 200
  161. Response headers:
  162. cache-control: no-cache
  163. client-request-id: a8cd2ffa-e896-43b6-84b2-6a940552e067
  164. content-type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
  165. pubDate: Wed, 01 Nov 2023 14:19:06 GMT
  166. odata-version: 4.0
  167. request-id: ecfd362f-3631-4c4d-aa2c-e8b84eb3911a
  168. strict-transport-security: max-age=31536000
  169. transfer-encoding: chunked
  170. vary: Accept-Encoding
  171. x-ms-ags-diagnostic: {&amp;quot;ServerInfo&amp;quot;:{&amp;quot;DataCenter&amp;quot;:&amp;quot;West Europe&amp;quot;,&amp;quot;Slice&amp;quot;:&amp;quot;E&amp;quot;,&amp;quot;Ring&amp;quot;:&amp;quot;5&amp;quot;,&amp;quot;ScaleUnit&amp;quot;:&amp;quot;005&amp;quot;,&amp;quot;RoleInstance&amp;quot;:&amp;quot;AM4PEPF00015137&amp;quot;}}
  172. x-ms-resource-unit: 1
  173. Response body:
  174. {&amp;quot;@odata.context&amp;quot;:&amp;quot;https://graph.microsoft.com/v1.0/$metadata#users&amp;quot;,&amp;quot;@odata.nextLink&amp;quot;:&amp;quot;https://graph.microsoft.com/v1.0/users?$top=1&amp;amp;$skiptoken=RFNwdAIAAQAAAB46QWRlbGVWQDJjemszZy5vbm1pY3Jvc29mdC5jb20pVXNlcl82ZGU4ZWMwNC02Mzc2LTQ5MzktYWI0Ny04M2EyYzg1YWI1ZjW5AAAAAAAAAAAAAA&amp;quot;,&amp;quot;value&amp;quot;:[{&amp;quot;businessPhones&amp;quot;:[&amp;quot;+1 425 555 0109&amp;quot;],&amp;quot;displayName&amp;quot;:&amp;quot;Adele Vance&amp;quot;,&amp;quot;givenName&amp;quot;:&amp;quot;Adele&amp;quot;,&amp;quot;jobTitle&amp;quot;:&amp;quot;Retail Manager&amp;quot;,&amp;quot;mail&amp;quot;:&amp;quot;AdeleV@2czk3g.onmicrosoft.com&amp;quot;,&amp;quot;mobilePhone&amp;quot;:null,&amp;quot;officeLocation&amp;quot;:&amp;quot;18/2111&amp;quot;,&amp;quot;preferredLanguage&amp;quot;:&amp;quot;en-US&amp;quot;,&amp;quot;surname&amp;quot;:&amp;quot;Vance&amp;quot;,&amp;quot;userPrincipalName&amp;quot;:&amp;quot;AdeleV@2czk3g.onmicrosoft.com&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;6de8ec04-6376-4939-ab47-83a2c85ab5f5&amp;quot;}]}
  175. &lt;/code&gt;&lt;/pre&gt;
  176. &lt;h2&gt;Summary&lt;/h2&gt;
  177. &lt;p&gt;Using the Microsoft Graph Java SDK is an easy way for you to get the data and insights from Microsoft 365 in your app. The SDK simplifies authentication and handles complex API scenarios for you. If you ever need to debug the requests and responses handled by the SDK, you can build a simple debug middleware to log the raw requests and responses for you.&lt;/p&gt;
  178. </description><pubDate>Wed, 01 Nov 2023 12:09:17 GMT</pubDate></item><item><title>Easily debug Microsoft Graph Python SDK requests</title><link>https://blog.mastykarz.nl/easily-debug-microsoft-graph-python-sdk-requests/</link><guid isPermaLink="true">https://blog.mastykarz.nl/easily-debug-microsoft-graph-python-sdk-requests/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/10/banner-graph-client-python-debug.png&quot; alt=&quot;Easily debug Microsoft Graph Python SDK requests&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;Using the Microsoft Graph Python SDK is a convenient way to connect your app to Microsoft 365. Here&apos;s how you can easily debug API requests issued from the SDK.&lt;/p&gt;
  179. &lt;h2&gt;Build apps that work with Microsoft 365&lt;/h2&gt;
  180. &lt;p&gt;Microsoft 365 is a platform where millions of users work together every day. You can create apps that use the data and insights from Microsoft 365 to help them do their work more efficiently and effectively.&lt;/p&gt;
  181. &lt;p&gt;Microsoft Graph is the API that connects you to your organization&apos;s data in Microsoft 365. You can access it using its REST endpoints. If you build an app connected to Microsoft 365 using Python, you should consider using the Microsoft Graph Python SDK. The SDK makes authentication easier and handles complex scenarios like backing off when throttled, following redirects and uploading large files, which means that you can focus on building your app instead of its plumbing!&lt;/p&gt;
  182. &lt;h2&gt;Use the Microsoft Graph Python SDK to connect your Python app to Microsoft 365&lt;/h2&gt;
  183. &lt;p&gt;To begin using the Microsoft Graph Python SDK, create a Graph client with an authentication provider. Then, you can start calling Microsoft Graph to access and manipulate data in Microsoft 365.&lt;/p&gt;
  184. &lt;p&gt;For building a deamon app, you&apos;d use code similar to following:&lt;/p&gt;
  185. &lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from azure.identity import ClientSecretCredential
  186. from msgraph import GraphServiceClient
  187.  
  188. _credential = ClientSecretCredential(_tenant_id, _client_id, _client_secret)
  189. _scopes = [&apos;https://graph.microsoft.com/.default&apos;]
  190.  
  191. graph_client = GraphServiceClient(_credential, _scopes)
  192. &lt;/code&gt;&lt;/pre&gt;
  193. &lt;h2&gt;Debug Microsoft Graph Python SDK API requests&lt;/h2&gt;
  194. &lt;p&gt;When you start building your app, it can happen that you&apos;ll get an error calling Microsoft Graph. Looking at the returned error information should give you a clearer idea of what&apos;s wrong and how to fix it.&lt;/p&gt;
  195. &lt;p&gt;If that&apos;s not the case though, and you need to see the request and response that goes over the wire, here&apos;s a trick that you can use.&lt;/p&gt;
  196. &lt;h3&gt;Microsoft Graph Python SDK middleware&lt;/h3&gt;
  197. &lt;p&gt;The Microsoft Graph Python SDK supports the concept of &lt;a href=&quot;https://learn.microsoft.com/graph/sdks/customize-client?tabs=python&quot;&gt;middleware&lt;/a&gt;: functions that run as part of its request- and response pipeline and have access to the information about the outgoing request and received response. If you want to inspect API requests and responses issued by the Microsoft Graph Python SDK, building a custom middleware is the easiest way about it. Middleware has access to the full request context information which includes information about the outgoing request and received response.&lt;/p&gt;
  198. &lt;h3&gt;Debug middleware for Microsoft Graph Python SDK&lt;/h3&gt;
  199. &lt;p&gt;Start, with creating a new file named &lt;code&gt;debug_handler.py&lt;/code&gt;. Add the following code:&lt;/p&gt;
  200. &lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from kiota_http.middleware import BaseMiddleware
  201. import httpx
  202.  
  203. class DebugHandler(BaseMiddleware):
  204.  
  205.    async def send(
  206.        self, request: httpx.Request, transport: httpx.AsyncBaseTransport
  207.    ) -&amp;gt; httpx.Response:
  208.        print()
  209.        print(f&amp;quot;{request.method} {request.url}&amp;quot;)
  210.        for key, value in request.headers.items():
  211.            print(f&amp;quot;{key}: {value}&amp;quot;)
  212.        if request.content:
  213.            print()
  214.            print(&amp;quot;Request body:&amp;quot;)
  215.            print(request.content.decode())
  216.  
  217.        response: httpx.Response = await super().send(request, transport)
  218.  
  219.        print()
  220.        print(f&amp;quot;Response: {response.status_code} {response.reason_phrase}&amp;quot;)
  221.        print(&amp;quot;Response headers:&amp;quot;)
  222.        for key, value in response.headers.items():
  223.            print(f&amp;quot;{key}: {value}&amp;quot;)
  224.  
  225.        print()
  226.        print(&amp;quot;Response body:&amp;quot;)
  227.        response_content = await response.aread()
  228.        print(f&amp;quot;Response content: {response_content.decode()}&amp;quot;)
  229.  
  230.        return response
  231. &lt;/code&gt;&lt;/pre&gt;
  232. &lt;p&gt;You start with defining a new handler that inherits from the BaseMiddleware class which defines middleware handlers for the Microsoft Graph Python SDK. Next, you implement the &lt;code&gt;send&lt;/code&gt; method, that is called on each API request handled by the SDK.&lt;/p&gt;
  233. &lt;p&gt;When this function runs as part of the client&apos;s middleware, it will first log information about the outgoing request. Then, it will call the next middleware in chain and wait for its execution. This is where the actual API request will be executed. Finally, it will print response result, its headers, and if suitable, the response body.&lt;/p&gt;
  234. &lt;h3&gt;Use the debug middleware&lt;/h3&gt;
  235. &lt;p&gt;The last step is to add the debug middleware to the Graph client. To do that, update the client&apos;s initiation code as follows:&lt;/p&gt;
  236. &lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from azure.identity import ClientSecretCredential
  237. from kiota_authentication_azure.azure_identity_authentication_provider import (
  238.    AzureIdentityAuthenticationProvider
  239. )
  240. from msgraph import GraphServiceClient, GraphRequestAdapter
  241. from msgraph_core import GraphClientFactory
  242. from debug_handler import DebugHandler
  243.  
  244. _credential = ClientSecretCredential(_tenant_id, _client_id, _client_secret)
  245. _auth_provider = AzureIdentityAuthenticationProvider(_credential)
  246.  
  247. _middleware = GraphClientFactory.get_default_middleware(None)
  248. _middleware.append(DebugHandler())
  249. _http_client = GraphClientFactory.create_with_custom_middleware(
  250.    _middleware
  251. )
  252. _adapter = GraphRequestAdapter(_auth_provider, _http_client)
  253.  
  254. graph_client = GraphServiceClient(
  255.    _credential,
  256.    scopes=[&amp;quot;https://graph.microsoft.com/.default&amp;quot;],
  257.    request_adapter=_adapter,
  258. )
  259. &lt;/code&gt;&lt;/pre&gt;
  260. &lt;p&gt;We changed the Graph client instantiation code, to get the default middleware chain, which includes authentication, handling throttling and ends with issuing the web request. For debugging to have access to all request and response information, we need to add it to the end of the middleware collection.&lt;/p&gt;
  261. &lt;p&gt;With this setup in place, when you call Microsoft Graph using the client, you&apos;ll see the following output in the console:&lt;/p&gt;
  262. &lt;blockquote&gt;
  263. &lt;p&gt;TIP: To see this middleware in action, check out the sample &lt;a href=&quot;https://adoption.microsoft.com/en-us/sample-solution-gallery/sample/pnp-graph-connector-python-markdown/&quot;&gt;Microsoft Graph connector&lt;/a&gt;.&lt;/p&gt;
  264. &lt;/blockquote&gt;
  265. &lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;$ python3 main.py
  266.  
  267. GET https://graph.microsoft.com/v1.0/users?$top=1
  268. host: graph.microsoft.com
  269. accept-encoding: gzip, deflate
  270. connection: keep-alive
  271. accept: application/json
  272. authorization: Bearer eyJ0eXAiOiJKV1QiL...
  273. user-agent: python-httpx/0.25.0 kiota-python/0.6.1
  274.  
  275. Response: 200 OK
  276. Response headers:
  277. cache-control: no-cache
  278. transfer-encoding: chunked
  279. content-type: application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
  280. content-encoding: gzip
  281. vary: Accept-Encoding
  282. strict-transport-security: max-age=31536000
  283. request-id: f7711087-2274-4077-9c47-c8909c18f7ba
  284. client-request-id: f7711087-2274-4077-9c47-c8909c18f7ba
  285. x-ms-ags-diagnostic: {&amp;quot;ServerInfo&amp;quot;:{&amp;quot;DataCenter&amp;quot;:&amp;quot;West Europe&amp;quot;,&amp;quot;Slice&amp;quot;:&amp;quot;E&amp;quot;,&amp;quot;Ring&amp;quot;:&amp;quot;5&amp;quot;,&amp;quot;ScaleUnit&amp;quot;:&amp;quot;004&amp;quot;,&amp;quot;RoleInstance&amp;quot;:&amp;quot;AM2PEPF0001D77E&amp;quot;}}
  286. x-ms-resource-unit: 1
  287. odata-version: 4.0
  288. pubDate: Tue, 24 Oct 2023 09:37:10 GMT
  289.  
  290. Response body:
  291. Response content: {&amp;quot;@odata.context&amp;quot;:&amp;quot;https://graph.microsoft.com/v1.0/$metadata#users&amp;quot;,&amp;quot;@odata.nextLink&amp;quot;:&amp;quot;https://graph.microsoft.com/v1.0/users?$top=1&amp;amp;$skiptoken=RFNwdAIAAQAAAB46QWRlbGVWQDJjemszZy5vbm1pY3Jvc29mdC5jb20pVXNlcl82ZGU4ZWMwNC02Mzc2LTQ5MzktYWI0Ny04M2EyYzg1YWI1ZjW5AAAAAAAAAAAAAA&amp;quot;,&amp;quot;value&amp;quot;:[{&amp;quot;businessPhones&amp;quot;:[&amp;quot;+1 425 555 0109&amp;quot;],&amp;quot;displayName&amp;quot;:&amp;quot;Adele Vance&amp;quot;,&amp;quot;givenName&amp;quot;:&amp;quot;Adele&amp;quot;,&amp;quot;jobTitle&amp;quot;:&amp;quot;Retail Manager&amp;quot;,&amp;quot;mail&amp;quot;:&amp;quot;AdeleV@2czk3g.onmicrosoft.com&amp;quot;,&amp;quot;mobilePhone&amp;quot;:null,&amp;quot;officeLocation&amp;quot;:&amp;quot;18/2111&amp;quot;,&amp;quot;preferredLanguage&amp;quot;:&amp;quot;en-US&amp;quot;,&amp;quot;surname&amp;quot;:&amp;quot;Vance&amp;quot;,&amp;quot;userPrincipalName&amp;quot;:&amp;quot;AdeleV@2czk3g.onmicrosoft.com&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;6de8ec04-6376-4939-ab47-83a2c85ab5f5&amp;quot;}]}
  292. &lt;/code&gt;&lt;/pre&gt;
  293. &lt;h2&gt;Summary&lt;/h2&gt;
  294. &lt;p&gt;Using the Microsoft Graph Python SDK is an easy way for you to get the data and insights from Microsoft 365 in your app. The SDK simplifies authentication and handles complex API scenarios for you. If you ever need to debug the requests and responses handled by the SDK, you can build a simple debug middleware to log the raw requests and responses for you.&lt;/p&gt;
  295. </description><pubDate>Tue, 24 Oct 2023 09:22:51 GMT</pubDate></item><item><title>Easily debug Microsoft Graph .NET SDK requests</title><link>https://blog.mastykarz.nl/easily-debug-microsoft-graph-dotnet-sdk-requests/</link><guid isPermaLink="true">https://blog.mastykarz.nl/easily-debug-microsoft-graph-dotnet-sdk-requests/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/10/banner-graph-client-dotnet-debug.png&quot; alt=&quot;Easily debug Microsoft Graph .NET SDK requests&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;Using the Microsoft Graph .NET SDK is a convenient way to connect your app to Microsoft 365. Here&apos;s how you can easily debug API requests issued from the SDK.&lt;/p&gt;
  296. &lt;h2&gt;Build apps that work with Microsoft 365&lt;/h2&gt;
  297. &lt;p&gt;Microsoft 365 is a platform where millions of users work together every day. You can create apps that use the data and insights from Microsoft 365 to help them do their work more efficiently and effectively.&lt;/p&gt;
  298. &lt;p&gt;Microsoft Graph is the API that connects you to your organization&apos;s data in Microsoft 365. You can access it using its REST endpoints. If you build an app connected to Microsoft 365 using .NET, you should consider using the Microsoft Graph .NET SDK. The SDK makes authentication easier and handles complex scenarios like backing off when throttled, following redirects and uploading large files, which means that you can focus on building your app instead of its plumbing!&lt;/p&gt;
  299. &lt;h2&gt;Use the Microsoft Graph .NET SDK to connect your .NET app to Microsoft 365&lt;/h2&gt;
  300. &lt;p&gt;To begin using the Microsoft Graph .NET SDK, create a Graph client with an authentication provider. Then, you can start calling Microsoft Graph to access and manipulate data in Microsoft 365.&lt;/p&gt;
  301. &lt;p&gt;For building a deamon app, you&apos;d use code similar to following:&lt;/p&gt;
  302. &lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;using Azure.Identity;
  303. using Microsoft.Graph;
  304.  
  305. var credential = new ClientSecretCredential(
  306.  tenantId,
  307.  clientId,
  308.  clientSecret
  309. );
  310. var scopes = new[] { &amp;quot;https://graph.microsoft.com/.default&amp;quot; };
  311. var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
  312. &lt;/code&gt;&lt;/pre&gt;
  313. &lt;h2&gt;Debug Microsoft Graph .NET SDK API requests&lt;/h2&gt;
  314. &lt;p&gt;When you start building your app, it can happen that you&apos;ll get an error calling Microsoft Graph. Looking at the returned error information should give you a clearer idea of what&apos;s wrong and how to fix it.&lt;/p&gt;
  315. &lt;p&gt;If that&apos;s not the case though, and you need to see the request and response that goes over the wire, here&apos;s a trick that you can use.&lt;/p&gt;
  316. &lt;h3&gt;Microsoft Graph .NET SDK middleware&lt;/h3&gt;
  317. &lt;p&gt;The Microsoft Graph .NET SDK supports the concept of &lt;a href=&quot;https://learn.microsoft.com/graph/sdks/customize-client?tabs=csharp&quot;&gt;middleware&lt;/a&gt;: functions that run as part of its request- and response pipeline and have access to the information about the outgoing request and received response. If you want to inspect API requests and responses issued by the Microsoft Graph .NET SDK, building a custom middleware is the easiest way about it. Middleware has access to the full request context information which includes information about the outgoing request and received response.&lt;/p&gt;
  318. &lt;h3&gt;Debug middleware for Microsoft Graph .NET SDK&lt;/h3&gt;
  319. &lt;p&gt;To create debug middleware for the Microsoft Graph .NET SDK, you have to split it into request- and response middleware. This has to do with how the middleware in the .NET SDK is ordered and called. Handlers are invoked in a top-down fashion. The first entry is invoked first for an outbound request message but last for an inbound response message.&lt;/p&gt;
  320. &lt;p&gt;To get full information about the outgoing request, you want the debug middleware to be the last middleware in the chain. But since decompression is done by the first middleware in the chain, to get access to human-readable response body, you need to add the debug middleware before the decompression middleware, which means adding it at the beginning of the collection.&lt;/p&gt;
  321. &lt;h4&gt;Log the request information&lt;/h4&gt;
  322. &lt;p&gt;Start, with creating a new file named &lt;code&gt;DebugRequestHandler.cs&lt;/code&gt;. Add the following code:&lt;/p&gt;
  323. &lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;class DebugRequestHandler : DelegatingHandler
  324. {
  325.  protected override async Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  326.  {
  327.    Console.WriteLine(&amp;quot;&amp;quot;);
  328.    Console.WriteLine(string.Format(&amp;quot;Request: {0} {1}&amp;quot;, request.Method, request.RequestUri));
  329.    Console.WriteLine(&amp;quot;Request headers:&amp;quot;);
  330.    foreach (var header in request.Headers)
  331.    {
  332.      Console.WriteLine(string.Format(&amp;quot;{0}: {1}&amp;quot;, header.Key, string.Join(&apos;,&apos;, header.Value)));
  333.    }
  334.    if (request.Content is not null)
  335.    {
  336.      Console.WriteLine(&amp;quot;&amp;quot;);
  337.      Console.WriteLine(&amp;quot;Request body:&amp;quot;);
  338.      var body = await request.Content.ReadAsStringAsync();
  339.      Console.WriteLine(body);
  340.    }
  341.  
  342.    return await base.SendAsync(request, cancellationToken);
  343.  }
  344. }
  345. &lt;/code&gt;&lt;/pre&gt;
  346. &lt;p&gt;The function follows the Microsoft Graph .NET SDK middleware pattern. It inherits from the &lt;code&gt;DelegatingHandler&lt;/code&gt; base class and overrides the &lt;code&gt;SendAsync&lt;/code&gt; method.&lt;/p&gt;
  347. &lt;p&gt;When the &lt;code&gt;SendAsync&lt;/code&gt; method is invoked, it will log the request method and URI. Next, it will list all request headers. Finally, if the request contains a body, it will print it too. In the end, it will pass the request to the middleware chain by calling &lt;code&gt;base.SendAsync()&lt;/code&gt;.&lt;/p&gt;
  348. &lt;h4&gt;Log the response information&lt;/h4&gt;
  349. &lt;p&gt;Next, create the &lt;code&gt;DebugResponseHandler.cs&lt;/code&gt; file where you&apos;ll implement the middleware to log information about the received response. Add the following code:&lt;/p&gt;
  350. &lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;class DebugResponseHandler : DelegatingHandler
  351. {
  352.  protected override async Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  353.  {
  354.    var response = await base.SendAsync(request, cancellationToken);
  355.  
  356.    Console.WriteLine(&amp;quot;&amp;quot;);
  357.    Console.WriteLine(&amp;quot;Response headers:&amp;quot;);
  358.    foreach (var header in response.Headers)
  359.    {
  360.      Console.WriteLine(string.Format(&amp;quot;{0}: {1}&amp;quot;, header.Key, string.Join(&apos;,&apos;, header.Value)));
  361.    }
  362.    if (response.Content is not null)
  363.    {
  364.      Console.WriteLine(&amp;quot;&amp;quot;);
  365.      Console.WriteLine(&amp;quot;Response body:&amp;quot;);
  366.      var body = await response.Content.ReadAsStringAsync();
  367.      Console.WriteLine(body);
  368.    }
  369.  
  370.    return response;
  371.  }
  372. }
  373. &lt;/code&gt;&lt;/pre&gt;
  374. &lt;p&gt;The execution starts with getting a response from the middleware chain by calling &lt;code&gt;base.SendAsync()&lt;/code&gt;.&lt;/p&gt;
  375. &lt;p&gt;Then, the middleware iterates over all response headers and prints them to console. If the response contains a body, it will print it out too.&lt;/p&gt;
  376. &lt;p&gt;Finally, it returns the response to the middleware chain.&lt;/p&gt;
  377. &lt;h3&gt;Use the debug middleware&lt;/h3&gt;
  378. &lt;p&gt;The last step is to add the debug middleware to the Graph client. To do that, update the client&apos;s initiation code as follows:&lt;/p&gt;
  379. &lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;using Azure.Identity;
  380. using Microsoft.Graph;
  381.  
  382. var credential = new ClientSecretCredential(
  383.  tenantId,
  384.  clientId,
  385.  clientSecret
  386. );
  387. var handlers = GraphClientFactory.CreateDefaultHandlers();
  388. // add at the end to get all information about the request
  389. handlers.Add(new DebugRequestHandler());
  390. // add at the beginning to get all information about the response
  391. // and also have the response body decompressed
  392. handlers.Insert(0, new DebugResponseHandler());
  393. var httpClient = GraphClientFactory.Create(handlers);
  394. var graphClient = new GraphServiceClient(httpClient, credential);
  395. &lt;/code&gt;&lt;/pre&gt;
  396. &lt;p&gt;We changed the Graph client instantiation code, to get the default middleware chain. Like I mentioned before, to get access to the full request information, the &lt;code&gt;DebugRequestHandler&lt;/code&gt; must be the last handler in the collection. To get access to the decompressed response body, the &lt;code&gt;DebugResponseHandler&lt;/code&gt; must be the first in the collection, right before the middleware responsible for decompression.&lt;/p&gt;
  397. &lt;p&gt;With this setup in place, when you call Microsoft Graph using the client, you&apos;ll see the following output in the console:&lt;/p&gt;
  398. &lt;blockquote&gt;
  399. &lt;p&gt;TIP: To see this middleware in action, check out the sample &lt;a href=&quot;https://adoption.microsoft.com/en-us/sample-solution-gallery/sample/pnp-graph-connector-dotnet-csharp-markdown/&quot;&gt;Microsoft Graph connector&lt;/a&gt;.&lt;/p&gt;
  400. &lt;/blockquote&gt;
  401. &lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;dotnet run
  402.  
  403. Request: GET https://graph.microsoft.com/v1.0/users?$top=1
  404. Request headers:
  405. Accept: application/json
  406. Authorization: Bearer eyJ0eXAiOiJKV1Qi...
  407. FeatureFlag: 00000043
  408. Cache-Control: no-store, no-cache
  409. Accept-Encoding: gzip
  410. User-Agent: kiota-dotnet/1.1.1
  411. SdkVersion:  graph-dotnet-core/3.0.11 (featureUsage=0000002B; hostOS=Unix 14.0.0; hostArch=X64; runtimeEnvironment=.NET 7.0.3;)
  412. client-request-id: bfbfd8a5-ce70-4950-83cf-851fb9a91c0a
  413.  
  414. Response headers:
  415. Cache-Control: no-cache
  416. Transfer-Encoding: chunked
  417. Vary: Accept-Encoding
  418. Strict-Transport-Security: max-age=31536000
  419. request-id: efb7b970-283f-4afc-9ce4-f28b0755940b
  420. client-request-id: bfbfd8a5-ce70-4950-83cf-851fb9a91c0a
  421. x-ms-ags-diagnostic: {&amp;quot;ServerInfo&amp;quot;:{&amp;quot;DataCenter&amp;quot;:&amp;quot;West Europe&amp;quot;,&amp;quot;Slice&amp;quot;:&amp;quot;E&amp;quot;,&amp;quot;Ring&amp;quot;:&amp;quot;5&amp;quot;,&amp;quot;ScaleUnit&amp;quot;:&amp;quot;004&amp;quot;,&amp;quot;RoleInstance&amp;quot;:&amp;quot;AM2PEPF0001D784&amp;quot;}}
  422. x-ms-resource-unit: 1
  423. OData-Version: 4.0
  424. pubDate: Fri, 13 Oct 2023 13:14:25 GMT
  425.  
  426. Response body:
  427. {&amp;quot;@odata.context&amp;quot;:&amp;quot;https://graph.microsoft.com/v1.0/$metadata#users&amp;quot;,&amp;quot;@odata.nextLink&amp;quot;:&amp;quot;https://graph.microsoft.com/v1.0/users?$top=1&amp;amp;$skiptoken=RFNwdAIAAQAAAB46QWRlbGVWQDJjemszZy5vbm1pY3Jvc29mdC5jb20pVXNlcl82ZGU4ZWMwNC02Mzc2LTQ5MzktYWI0Ny04M2EyYzg1YWI1ZjW5AAAAAAAAAAAAAA&amp;quot;,&amp;quot;value&amp;quot;:[{&amp;quot;businessPhones&amp;quot;:[&amp;quot;+1 425 555 0109&amp;quot;],&amp;quot;displayName&amp;quot;:&amp;quot;Adele Vance&amp;quot;,&amp;quot;givenName&amp;quot;:&amp;quot;Adele&amp;quot;,&amp;quot;jobTitle&amp;quot;:&amp;quot;Retail Manager&amp;quot;,&amp;quot;mail&amp;quot;:&amp;quot;AdeleV@2czk3g.onmicrosoft.com&amp;quot;,&amp;quot;mobilePhone&amp;quot;:null,&amp;quot;officeLocation&amp;quot;:&amp;quot;18/2111&amp;quot;,&amp;quot;preferredLanguage&amp;quot;:&amp;quot;en-US&amp;quot;,&amp;quot;surname&amp;quot;:&amp;quot;Vance&amp;quot;,&amp;quot;userPrincipalName&amp;quot;:&amp;quot;AdeleV@2czk3g.onmicrosoft.com&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;6de8ec04-6376-4939-ab47-83a2c85ab5f5&amp;quot;}]}
  428. &lt;/code&gt;&lt;/pre&gt;
  429. &lt;h2&gt;Summary&lt;/h2&gt;
  430. &lt;p&gt;Using the Microsoft Graph .NET SDK is an easy way for you to get the data and insights from Microsoft 365 in your app. The SDK simplifies authentication and handles complex API scenarios for you. If you ever need to debug the requests and responses handled by the SDK, you can build a simple debug middleware to log the raw requests and responses for you.&lt;/p&gt;
  431. </description><pubDate>Fri, 13 Oct 2023 13:24:55 GMT</pubDate></item><item><title>Easily handle long-running operations using middleware in the Microsoft Graph .NET SDK</title><link>https://blog.mastykarz.nl/easily-handle-long-running-operations-middleware-microsoft-graph-net-sdk/</link><guid isPermaLink="true">https://blog.mastykarz.nl/easily-handle-long-running-operations-middleware-microsoft-graph-net-sdk/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/10/banner-middleware-longrunningoperation-dotnet.png&quot; alt=&quot;Easily handle long-running operations using middleware in the Microsoft Graph .NET SDK&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;If you use the Microsoft Graph .NET SDK and need to handle long-running operations, build it as a middleware. Here&apos;s how.&lt;/p&gt;
  432. &lt;h2&gt;Why you should consider to use a Microsoft Graph SDK&lt;/h2&gt;
  433. &lt;p&gt;Microsoft Graph .NET SDK offers you a convenient way to connect to Microsoft Graph - the API to data and insights on Microsoft 365. The SDK takes care of authentication and other request-related plumbing helping you to focus on building your app.&lt;/p&gt;
  434. &lt;h2&gt;Long-running operations on Microsoft Graph&lt;/h2&gt;
  435. &lt;p&gt;While most operations on Microsoft Graph are instantaneous, there are some exceptions. One example is creating a Microsoft Graph connector schema which can take several minutes. After you submit the schema, you get back a 202 Accepted response, with a &lt;code&gt;Location&lt;/code&gt; header with the URL that you can call to check the status of the schema creation operation.&lt;/p&gt;
  436. &lt;blockquote&gt;
  437. &lt;p&gt;TIP: If you&apos;re building code to handle the long-running operation of creating the Microsoft Graph connector schema, use the &lt;a href=&quot;https://adoption.microsoft.com/sample-solution-gallery/sample/pnp-microsoft-graph-connector&quot;&gt;Microsoft 365 Developer Proxy mock&lt;/a&gt; to simulate the API behavior. Using the mock you&apos;ll be able to easily test your code, without having to wait for the schema to be provisioned each time. It&apos;ll save you a lot of time!&lt;/p&gt;
  438. &lt;/blockquote&gt;
  439. &lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;POST https://graph.microsoft.com/v1.0/external/connections/contosohr/schema
  440. content-type: application/json
  441.  
  442. {
  443.  &amp;quot;baseType&amp;quot;: &amp;quot;microsoft.graph.externalItem&amp;quot;,
  444.  &amp;quot;properties&amp;quot;: [
  445.    {
  446.      &amp;quot;name&amp;quot;: &amp;quot;ticketTitle&amp;quot;,
  447.      &amp;quot;type&amp;quot;: &amp;quot;String&amp;quot;,
  448.      &amp;quot;isSearchable&amp;quot;: &amp;quot;true&amp;quot;,
  449.      &amp;quot;isRetrievable&amp;quot;: &amp;quot;true&amp;quot;,
  450.      &amp;quot;labels&amp;quot;: [
  451.        &amp;quot;title&amp;quot;
  452.      ]
  453.    },
  454.    {
  455.      &amp;quot;name&amp;quot;: &amp;quot;priority&amp;quot;,
  456.      &amp;quot;type&amp;quot;: &amp;quot;String&amp;quot;,
  457.      &amp;quot;isQueryable&amp;quot;: &amp;quot;true&amp;quot;,
  458.      &amp;quot;isRetrievable&amp;quot;: &amp;quot;true&amp;quot;,
  459.      &amp;quot;isSearchable&amp;quot;: &amp;quot;false&amp;quot;
  460.    },
  461.    {
  462.      &amp;quot;name&amp;quot;: &amp;quot;assignee&amp;quot;,
  463.      &amp;quot;type&amp;quot;: &amp;quot;String&amp;quot;,
  464.      &amp;quot;isRetrievable&amp;quot;: &amp;quot;true&amp;quot;
  465.    }
  466.  ]
  467. }
  468.  
  469. 202 Accepted
  470. Location: https://graph.microsoft.com/v1.0/external/connections/contosohr/operations/616bfeed-666f-4ce0-8cd9-058939010bfc
  471. &lt;/code&gt;&lt;/pre&gt;
  472. &lt;p&gt;Calling the URL gives you the current status of the creation operation:&lt;/p&gt;
  473. &lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;GET https://graph.microsoft.com/v1.0/external/connections/contosohr/operations/616bfeed-666f-4ce0-8cd9-058939010bfc
  474.  
  475. 200 OK
  476. content-type: application/json
  477.  
  478. {
  479.  &amp;quot;id&amp;quot;: &amp;quot;1.neu.0278337E599FC8DBF5607ED12CF463E4.6410CCF8F6DB8758539FB58EB56BF8DC&amp;quot;,
  480.  &amp;quot;status&amp;quot;: &amp;quot;inprogress&amp;quot;,
  481.  &amp;quot;error&amp;quot;: null
  482. }
  483. &lt;/code&gt;&lt;/pre&gt;
  484. &lt;p&gt;If you want to wait until the operation completes, you need to poll the URL at regular intervals, until the &lt;code&gt;status&lt;/code&gt; changes from &lt;code&gt;inprogress&lt;/code&gt; to either &lt;code&gt;completed&lt;/code&gt; or &lt;code&gt;failed&lt;/code&gt;.&lt;/p&gt;
  485. &lt;blockquote&gt;
  486. &lt;p&gt;TIP: For checking the status of creating Microsoft Graph connector schema, Microsoft recommends polling the status every 1 minute.&lt;/p&gt;
  487. &lt;/blockquote&gt;
  488. &lt;p&gt;As you can imagine, handling this flow adds a lot of code. A lot of code that makes it harder to understand what your app is doing exactly, but which you need nonetheless. Luckily, you don&apos;t need to put it in the main flow of your app. By implementing it as a middleware, you can make it readily available to any piece of your code that needs it without having to invoke it explicitly.&lt;/p&gt;
  489. &lt;h2&gt;What&apos;s middleware&lt;/h2&gt;
  490. &lt;p&gt;Microsoft Graph SDKs come with the concept of &lt;em&gt;middleware&lt;/em&gt;, also known as &lt;em&gt;handlers&lt;/em&gt;. Middleware are pieces of code that handle outgoing requests and incoming responses. They&apos;re applied in a predefined order and run on every request you issue using the Microsoft Graph SDK.&lt;/p&gt;
  491. &lt;p&gt;Microsoft Graph SDKs ship with several middleware handlers and you can add your own to the pipeline too. What&apos;s neat about middleware, is that it&apos;s automatically applied to every request and doesn&apos;t clutter your main code, making it easier to follow.&lt;/p&gt;
  492. &lt;h2&gt;Handle long-running operations using middleware&lt;/h2&gt;
  493. &lt;p&gt;The following is C# code that you can use to handle the long-running operation of creating Microsoft Graph connector schema when using the Microsoft Graph .NET SDK:&lt;/p&gt;
  494. &lt;blockquote&gt;
  495. &lt;p&gt;TIP: To see this middleware in action, check out the &lt;a href=&quot;https://github.com/pnp/graph-connectors-samples/tree/main/samples/dotnet-csharp-markdown&quot;&gt;Microsoft Graph connector sample&lt;/a&gt; built using .NET that imports markdown content to Microsoft 365.&lt;/p&gt;
  496. &lt;/blockquote&gt;
  497. &lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;using Microsoft.Graph.Models.ExternalConnectors;
  498. using Microsoft.Kiota.Abstractions.Serialization;
  499.  
  500. class CompleteJobWithDelayHandler : DelegatingHandler
  501. {
  502.  int delayMs;
  503.  
  504.  public CompleteJobWithDelayHandler(int delayMs = 60000)
  505.  {
  506.    this.delayMs = delayMs;
  507.  }
  508.  
  509.  protected override async Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  510.  {
  511.    var response = await base.SendAsync(request, cancellationToken);
  512.  
  513.    var location = response.Headers.FirstOrDefault(h =&amp;gt; h.Key == &amp;quot;Location&amp;quot;).Value?.FirstOrDefault();
  514.    if (location is not null)
  515.    {
  516.      if (location.IndexOf(&amp;quot;/operations/&amp;quot;) &amp;lt; 0)
  517.      {
  518.        // not a job URL we should follow
  519.        return response;
  520.      }
  521.  
  522.      Console.WriteLine(string.Format(&amp;quot;Location: {0}&amp;quot;, location));
  523.  
  524.      // string interpolation causes NullReferenceException on macOS x64
  525.      // for some reason here, so need to use String.Format instead
  526.      Console.WriteLine(string.Format(&amp;quot;Waiting {0}ms before following location {1}...&amp;quot;, delayMs, location));
  527.      await Task.Delay(delayMs);
  528.  
  529.      request.RequestUri = new Uri(location);
  530.      request.Method = HttpMethod.Get;
  531.      request.Content = null;
  532.  
  533.      var cts = new CancellationTokenSource();
  534.      cts.CancelAfter(TimeSpan.FromMinutes(25));
  535.      return await SendAsync(request, cts.Token);
  536.    }
  537.  
  538.    if (!response.IsSuccessStatusCode)
  539.    {
  540.      Console.WriteLine(string.Format(&amp;quot;Response is not successful: {0}&amp;quot;, response.StatusCode));
  541.      try
  542.      {
  543.        var errorBody = await response.Content.ReadAsStringAsync();
  544.        Console.WriteLine(errorBody);
  545.      }
  546.      catch { }
  547.      return response;
  548.    }
  549.  
  550.    if (request.RequestUri?.AbsolutePath.IndexOf(&amp;quot;/operations/&amp;quot;) &amp;lt; 0)
  551.    {
  552.      // not a job
  553.      return response;
  554.    }
  555.  
  556.    var body = await response.Content.ReadAsStringAsync();
  557.  
  558.    // deserialize the response
  559.    using var ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(body));
  560.    var parseNode = ParseNodeFactoryRegistry.DefaultInstance.GetRootParseNode(&amp;quot;application/json&amp;quot;, ms);
  561.    var operation = parseNode.GetObjectValue(ConnectionOperation.CreateFromDiscriminatorValue);
  562.  
  563.    if (operation?.Status == ConnectionOperationStatus.Inprogress)
  564.    {
  565.      // string interpolation causes NullReferenceException on macOS x64
  566.      // for some reason here, so need to use String.Format instead
  567.      Console.WriteLine(string.Format(&amp;quot;Waiting {0}ms before retrying {1}...&amp;quot;, delayMs, request.RequestUri));
  568.      await Task.Delay(delayMs);
  569.      return await SendAsync(request, cancellationToken);
  570.    }
  571.    else
  572.    {
  573.      return response;
  574.    }
  575.  }
  576. }
  577. &lt;/code&gt;&lt;/pre&gt;
  578. &lt;p&gt;The middleware starts with executing the request and capturing the response. Then, it checks if the response contains the &lt;code&gt;Location&lt;/code&gt; header.&lt;/p&gt;
  579. &lt;p&gt;If it does, it checks if the URL in the &lt;code&gt;Location&lt;/code&gt; header contains the &lt;code&gt;/operations/&lt;/code&gt; segment. Remember, the middleware runs on every request so before processing it, you need to be sure that you&apos;re looking at the right request/response.&lt;/p&gt;
  580. &lt;p&gt;If the URL in the &lt;code&gt;Location&lt;/code&gt; header doesn&apos;t contain the &lt;code&gt;/operations/&lt;/code&gt; segment, the middleware stops its execution.&lt;/p&gt;
  581. &lt;p&gt;If it does contain the &lt;code&gt;/operations/&lt;/code&gt; segment, the middleware waits for the specified amount of time. Then, it updates the information about the request, mapping it to the URL from the Location header, updating its method to GET, and clearing its body. Additionally, it defines a new cancellation token set to 25 minutes to avoid the request timing out in 100 seconds which is the default behavior. With these updates in place, the middleware re-issues the request.&lt;/p&gt;
  582. &lt;p&gt;Going back to the main code flow, if the request doesn&apos;t return a successful response, the middleware will return the response to the caller. It&apos;ll also return the response if the request doesn&apos;t have the &lt;code&gt;/operations/&lt;/code&gt; segment.&lt;/p&gt;
  583. &lt;p&gt;If the request URL does contain the &lt;code&gt;/operations/&lt;/code&gt; segment, then the middleware reads the body to check the status of the operation. Here, it uses Kiota&apos;s (technology that underpins the Microsoft Graph .NET SDK) deserialization capabilities to convert the response body to a Microsoft Graph entity that represents the connection operation.&lt;/p&gt;
  584. &lt;p&gt;If the operation is in progress, the middleware will wait and call the status URL again. If the status is different, it will return the response to the caller.&lt;/p&gt;
  585. &lt;p&gt;It&apos;s a lot of code, isn&apos;t it? What&apos;s really cool about it though, is that because it&apos;s implemented in the middleware, it&apos;s invisible in your main code flow, where the only thing you&apos;ll see is:&lt;/p&gt;
  586. &lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// GraphService.Client is an instance of GraphServiceClient
  587. await GraphService.Client.External
  588.  .Connections[ConnectionConfiguration.ExternalConnection.Id]
  589.  .Schema
  590.  .PatchAsync(ConnectionConfiguration.Schema);
  591. &lt;/code&gt;&lt;/pre&gt;
  592. &lt;p&gt;See how simple to understand this code is? It&apos;s only possible because the complexity of handling the long-running operation has been put in middleware.&lt;/p&gt;
  593. &lt;p&gt;To use this middleware, add it to the code where you instantiate &lt;code&gt;GraphClient&lt;/code&gt;:&lt;/p&gt;
  594. &lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;using Azure.Identity;
  595. using Microsoft.Graph;
  596.  
  597. // get default middleware
  598. var handlers = GraphClientFactory.CreateDefaultHandlers();
  599. // add the long-running operation middleware as first
  600. handlers.Insert(0, new CompleteJobWithDelayHandler(60000));
  601.  
  602. var httpClient = GraphClientFactory.Create(handlers);
  603. var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
  604. var client = new GraphServiceClient(httpClient, credential);
  605. &lt;/code&gt;&lt;/pre&gt;
  606. &lt;p&gt;In the Microsoft Graph .NET SDK, auth is handled separately, so you can add the long-running operation middleware as the first middleware in the collection to ensure that it runs first in the pipeline.&lt;/p&gt;
  607. &lt;h2&gt;Summary&lt;/h2&gt;
  608. &lt;p&gt;Using the Microsoft Graph .NET SDK is an easy way for you to get the data and insights from Microsoft 365 in your app. The SDK simplifies authentication and handles complex API scenarios for you. If you need to handle long-running operations, like creating Microsoft Graph connector schema, implement it as middleware to keep your main code flow clean and make the logic available throughout your app without repeating the code.&lt;/p&gt;
  609. </description><pubDate>Tue, 10 Oct 2023 09:00:31 GMT</pubDate></item><item><title>Create an Entra app with API permissions, admin consent and a secret using Microsoft Graph PowerShell SDK</title><link>https://blog.mastykarz.nl/create-entra-app-api-permissions-admin-consent-secret-microsoft-graph-powershell-sdk/</link><guid isPermaLink="true">https://blog.mastykarz.nl/create-entra-app-api-permissions-admin-consent-secret-microsoft-graph-powershell-sdk/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/10/banner-entra-app-microsoft-graph-powershell-sdk.png&quot; alt=&quot;Create an Entra app with API permissions, admin consent and a secret using Microsoft Graph PowerShell SDK&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;When building apps for Microsoft 365, the first step is to create an Entra app registration. Here&apos;s how to do it using Microsoft Graph PowerShell SDK.&lt;/p&gt;
  610. &lt;h2&gt;First things first: Entra app registrations&lt;/h2&gt;
  611. &lt;p&gt;In my &lt;a href=&quot;https://blog.mastykarz.nl/create-entra-app-api-permissions-admin-consent-secret-cli-microsoft-365&quot;&gt;previous article&lt;/a&gt;, I told you about the necessity of having an Entra app registration when building apps for Microsoft 365. I also showed you, how to script creating and configuring an Entra app registration using the CLI for Microsoft 365. If you&apos;re using PowerShell you might be more inclined to use the Microsoft Graph PowerShell SDK instead, and here&apos;s how you can write a similar script using it.&lt;/p&gt;
  612. &lt;h2&gt;What&apos;s Microsoft Graph PowerShell SDK&lt;/h2&gt;
  613. &lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/powershell/microsoftgraph/overview?view=graph-powershell-1.0&quot;&gt;Microsoft Graph PowerShell SDK&lt;/a&gt; is a set of cmdlets that wrap Microsoft Graph APIs and expose them in PowerShell. Because the SDK manages auth for you, it&apos;s a very convenient way of using Microsoft Graph APIs for writing automation scripts.&lt;/p&gt;
  614. &lt;h2&gt;Create Entra app registration using Microsoft Graph PowerShell SDK&lt;/h2&gt;
  615. &lt;p&gt;Here&apos;s a PowerShell script that creates an Entra app registration for use with a Microsoft Graph connector. The script configures API permissions, grants admin consent, creates a secret, and stores the information in .NET user secrets for use with your application.&lt;/p&gt;
  616. &lt;p&gt;Let&apos;s go through it step by step.&lt;/p&gt;
  617. &lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;Connect-MgGraph -Scopes AppRoleAssignment.ReadWrite.All,Application.ReadWrite.All -NoWelcome
  618.  
  619. $requiredResourceAccess = (@{
  620.  &amp;quot;resourceAccess&amp;quot; = (
  621.    @{
  622.      id = &amp;quot;f431331c-49a6-499f-be1c-62af19c34a9d&amp;quot;
  623.      type = &amp;quot;Role&amp;quot;
  624.    },
  625.    @{
  626.      id = &amp;quot;8116ae0f-55c2-452d-9944-d18420f5b2c8&amp;quot;
  627.      type = &amp;quot;Role&amp;quot;
  628.    }
  629.  )
  630.  &amp;quot;resourceAppId&amp;quot; = &amp;quot;00000003-0000-0000-c000-000000000000&amp;quot;
  631. })
  632.  
  633. # create the application
  634. $app = New-MgApplication -DisplayName &amp;quot;Waldek Mastykarz (blog) - connector (.net)&amp;quot; -RequiredResourceAccess $requiredResourceAccess
  635.  
  636. # grant admin consent
  637. $graphSpId = $(Get-MgServicePrincipal -Filter &amp;quot;appId eq &apos;00000003-0000-0000-c000-000000000000&apos;&amp;quot;).Id
  638. $sp = New-MgServicePrincipal -AppId $app.appId
  639. New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -PrincipalId $sp.Id -AppRoleId &amp;quot;f431331c-49a6-499f-be1c-62af19c34a9d&amp;quot; -ResourceId $graphSpId
  640. New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id -PrincipalId $sp.Id -AppRoleId &amp;quot;8116ae0f-55c2-452d-9944-d18420f5b2c8&amp;quot; -ResourceId $graphSpId
  641.  
  642. # create client secret
  643. $cred = Add-MgApplicationPassword -ApplicationId $app.id
  644.  
  645. # store values in user secrets
  646. dotnet user-secrets init
  647. dotnet user-secrets set &amp;quot;AzureAd:ClientId&amp;quot; $app.appId
  648. dotnet user-secrets set &amp;quot;AzureAd:ClientSecret&amp;quot; $cred.secretText
  649. dotnet user-secrets set &amp;quot;AzureAd:TenantId&amp;quot; $($(Get-MgContext).TenantId)
  650. &lt;/code&gt;&lt;/pre&gt;
  651. &lt;h3&gt;Sign in with Microsoft Graph PowerShell SDK&lt;/h3&gt;
  652. &lt;p&gt;Before you can use Microsoft Graph PowerShell SDK, you need to log in with your work or school account. You do this using the &lt;code&gt;Connect-MgGraph&lt;/code&gt; cmdlet. While invoking it, you need to specify the list of scopes that you&apos;ll need for your script.&lt;/p&gt;
  653. &lt;blockquote&gt;
  654. &lt;p&gt;TIP: To find out if you&apos;re using the minimal permissions for your app, use the &lt;a href=&quot;https://github.com/microsoft/m365-developer-proxy/wiki/Check-if-you-are-using-excessive-Microsoft-Graph-API-permissions&quot;&gt;Microsoft 365 Developer Proxy&lt;/a&gt;.&lt;/p&gt;
  655. &lt;/blockquote&gt;
  656. &lt;h3&gt;Create Entra app registration&lt;/h3&gt;
  657. &lt;p&gt;After signing in with Microsoft Graph PowerShell SDK, we use the &lt;code&gt;New-MgApplication&lt;/code&gt; cmdlet to create a new Entra app registration. Along with the name, we use the &lt;code&gt;RequiredResourceAccess&lt;/code&gt; argument to specify the list of API permissions to grant the application. This definition seems a bit cryptic, and while you could extend the script to look it up based on the scope names, another way to get it, is to configure the Entra app registration manually in the Azure Portal and get the API permission information from the app&apos;s manifest.&lt;/p&gt;
  658. &lt;p&gt;&lt;picture&gt;
  659.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2023/10/entra-app-registration-manifest-azure-portal.webp&quot; type=&quot;image/webp&quot;&gt;
  660.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/10/entra-app-registration-manifest-azure-portal.png&quot; alt=&quot;API permission highlighted in the app&apos;s manifest in the Azure Portal&quot;&gt;
  661. &lt;/picture&gt;&lt;/p&gt;
  662. &lt;h3&gt;Grant admin consent&lt;/h3&gt;
  663. &lt;p&gt;Next, we&apos;ll grant admin consent for API permissions that we&apos;ve just set on our app. To do this, we first need the object ID of the Microsoft Graph service principal. We retrieve it, using the &lt;code&gt;Get-MgServicePrincipal&lt;/code&gt; cmdlet filtering by the well-known Microsoft Graph app ID.&lt;/p&gt;
  664. &lt;p&gt;Then, we create a new service principal for our Entra app registration. We do this using the &lt;code&gt;New-MgServicePrincipal&lt;/code&gt; cmdlet passing the app ID of our newly created Entra app registration.&lt;/p&gt;
  665. &lt;p&gt;With these prerequisites in place, we&apos;re ready to grant admin consent for the API permissions for our app. For each permission, we invoke the &lt;code&gt;New-MgServicePrincipalAppRoleAssignment&lt;/code&gt; cmdlet passing the relevant information through args: both &lt;code&gt;ServicePrincipalId&lt;/code&gt; and &lt;code&gt;PrincipalId&lt;/code&gt; point to the object ID of the service principal of our Entra app registration, &lt;code&gt;ResourceId&lt;/code&gt; references the object ID of the Microsoft Graph service principal and &lt;code&gt;AppRoleId&lt;/code&gt; points to the application permissions for which we want to grant consent.&lt;/p&gt;
  666. &lt;h3&gt;Create secret&lt;/h3&gt;
  667. &lt;p&gt;The next step is to create a client secret to make it possible to authenticate with application permissions. We do this using the &lt;code&gt;Add-MgApplicationPassword&lt;/code&gt; cmdlet, passing the object ID of our Entra app registration.&lt;/p&gt;
  668. &lt;h3&gt;Store application information for use with the application&lt;/h3&gt;
  669. &lt;p&gt;If you build an app for Microsoft 365 using .NET, you can use the &lt;code&gt;dotnet user-secret&lt;/code&gt; command to securely store secrets for use with your app. Start with initiating a secret store for your app by calling &lt;code&gt;dotnet user-secret init&lt;/code&gt;. Then, for each secret to store, use the &lt;code&gt;dotnet user-secret set&lt;/code&gt; command passing the name and the value of the secret.&lt;/p&gt;
  670. &lt;p&gt;Since the Entra app registration that we created only accepts authentication from accounts on the same tenant, we also need to store the tenant ID which we&apos;ll need in our app for Microsoft 365. We can get it using the &lt;code&gt;Get-MgContext&lt;/code&gt; cmdlet.&lt;/p&gt;
  671. &lt;p&gt;To see this script in action, check out &lt;a href=&quot;https://github.com/pnp/graph-connectors-samples/blob/3a3048ef216b30410d5fb4af77680ff604536cde/samples/dotnet-csharp-markdown/setup.ps1&quot;&gt;this sample Microsoft Graph connector&lt;/a&gt; that uses it.&lt;/p&gt;
  672. </description><pubDate>Sat, 07 Oct 2023 08:10:03 GMT</pubDate></item><item><title>Create an Entra app with API permissions, admin consent and a secret using CLI for Microsoft 365</title><link>https://blog.mastykarz.nl/create-entra-app-api-permissions-admin-consent-secret-cli-microsoft-365/</link><guid isPermaLink="true">https://blog.mastykarz.nl/create-entra-app-api-permissions-admin-consent-secret-cli-microsoft-365/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/10/banner-entra-app-cli-microsoft365.png&quot; alt=&quot;Create an Entra app with API permissions, admin consent and a secret using CLI for Microsoft 365&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;When building apps for Microsoft 365, the first step is to create an Entra app registration. Here&apos;s how to do it using CLI for Microsoft 365.&lt;/p&gt;
  673. &lt;h2&gt;First things first: Entra app registrations&lt;/h2&gt;
  674. &lt;p&gt;When you build apps for Microsoft 365, to access data and insights from Microsoft 365, or even just to let users sign in to your app using their work or school account, you&apos;ll need an Entra app registration. An app registration registers your app with the Microsoft Cloud and defines settings such as API permissions or authentication flows.&lt;/p&gt;
  675. &lt;p&gt;You can create an Entra app registration in the Azure portal where you can use the different screens to configure the necessary settings for your app. If you&apos;re building a template for your organization, or want to provide your customers with an automated way, you can script creating an Entra app registration. Here&apos;s how to do it, using CLI for Microsoft 365.&lt;/p&gt;
  676. &lt;h2&gt;What&apos;s CLI for Microsoft 365&lt;/h2&gt;
  677. &lt;p&gt;&lt;a href=&quot;https://aka.ms/cli-m365&quot;&gt;CLI for Microsoft 365&lt;/a&gt; is an open-source, community-built, cross-platform command-line tool, that allows you to manage different aspects of Microsoft 365 and SharePoint Framework projects. It runs on Node.js and you can run it both locally as well as on serverless, containers and in CI/CD.&lt;/p&gt;
  678. &lt;h2&gt;Create Entra app registration using CLI for Microsoft 365&lt;/h2&gt;
  679. &lt;p&gt;Here&apos;s a bash script that creates an Entra app registration for use with a Microsoft Graph connector. The script configures API permissions, grants admin consent, creates a secret, and stores the information in a local file for use with your application.&lt;/p&gt;
  680. &lt;p&gt;Let&apos;s go through it step by step.&lt;/p&gt;
  681. &lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
  682.  
  683. # login
  684. echo &amp;quot;Sign in to Microsoft 365...&amp;quot;
  685. npx -p @pnp/cli-microsoft365 -- m365 login --authType browser
  686.  
  687. # create AAD app
  688. echo &amp;quot;Creating AAD app...&amp;quot;
  689. appInfo=$(npx -p @pnp/cli-microsoft365 -- m365 aad app add --name &amp;quot;Waldek Mastykarz (blog) - connector&amp;quot; --withSecret --apisApplication &amp;quot;https://graph.microsoft.com/ExternalConnection.ReadWrite.OwnedBy, https://graph.microsoft.com/ExternalItem.ReadWrite.OwnedBy&amp;quot; --grantAdminConsent --output json)
  690.  
  691. # write app to env.js
  692. echo &amp;quot;Writing app to env.js...&amp;quot;
  693. echo &amp;quot;export const appInfo = $appInfo;&amp;quot; &amp;gt; env.js
  694.  
  695. echo &amp;quot;DONE&amp;quot;
  696. &lt;/code&gt;&lt;/pre&gt;
  697. &lt;p&gt;Before you can use CLI for Microsoft 365, you need to log in with your work or school account. You do this using the &lt;code&gt;m365 login&lt;/code&gt; command.&lt;/p&gt;
  698. &lt;p&gt;Rather than invoking the command directly, we use &lt;code&gt;npx&lt;/code&gt;. &lt;code&gt;npx&lt;/code&gt; is a tool provided with &lt;code&gt;npm&lt;/code&gt; which allows you to start npm-distributed tools without having to install them first. This is a convenient way to share scripts with others who might not have CLI for Microsoft 365 installed yet.&lt;/p&gt;
  699. &lt;p&gt;After signing in with CLI for Microsoft 365, we use the &lt;code&gt;m365 aad app add&lt;/code&gt; command to create the app registration. This command is optimized for creating Entra app registrations, which is why it offers you convenient options to specify configuration settings easily. In a human-readable way we specify, that we want to create a new app registration with &lt;code&gt;ExternalConnection.ReadWrite.OwnedBy&lt;/code&gt; and &lt;code&gt;ExternalItem.ReadWrite.OwnedBy&lt;/code&gt; Microsoft Graph application permissions, we want to grant admin consent (&lt;code&gt;--grantAdminContent&lt;/code&gt;) and create a secret (&lt;code&gt;--withSecret&lt;/code&gt;).&lt;/p&gt;
  700. &lt;p&gt;Finally, we store the output of the command in a variable and then write it to a local file, which we can use in our application to authenticate.&lt;/p&gt;
  701. &lt;p&gt;To see this script in action, check out &lt;a href=&quot;https://github.com/pnp/graph-connectors-samples/tree/3a3048ef216b30410d5fb4af77680ff604536cde/samples/nodejs-javascript-solutiongallery&quot;&gt;this sample Microsoft Graph connector&lt;/a&gt; that uses it.&lt;/p&gt;
  702. </description><pubDate>Fri, 06 Oct 2023 11:14:02 GMT</pubDate></item><item><title>Easily handle long-running operations using middleware in the Microsoft Graph JavaScript SDK</title><link>https://blog.mastykarz.nl/easily-handle-long-running-operations-middleware-microsoft-graph-javascript-sdk/</link><guid isPermaLink="true">https://blog.mastykarz.nl/easily-handle-long-running-operations-middleware-microsoft-graph-javascript-sdk/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/09/banner-middleware-longrunningoperation.png&quot; alt=&quot;Easily handle long-running operations using middleware in the Microsoft Graph JavaScript SDK&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;If you use the Microsoft Graph JavaScript SDK and need to handle long-running operations, build it as a middleware. Here&apos;s how.&lt;/p&gt;
  703. &lt;h2&gt;Why you should consider to use a Microsoft Graph SDK&lt;/h2&gt;
  704. &lt;p&gt;Microsoft Graph JavaScript SDK offers you a convenient way to connect to Microsoft Graph - the API to data and insights on Microsoft 365. The SDK takes care of authentication and other request-related plumbing helping you to focus on building your app.&lt;/p&gt;
  705. &lt;h2&gt;Long-running operations on Microsoft Graph&lt;/h2&gt;
  706. &lt;p&gt;While most operations on Microsoft Graph are instantaneous, there are some exceptions. One example is creating a Microsoft Graph connector schema which can take several minutes. After you submit the schema, you get back a 202 Accepted response, with a &lt;code&gt;Location&lt;/code&gt; header with the URL that you can call to check the status of the schema creation operation.&lt;/p&gt;
  707. &lt;blockquote&gt;
  708. &lt;p&gt;TIP: If you&apos;re building code to handle the long-running operation of creating the Microsoft Graph connector schema, use the &lt;a href=&quot;https://adoption.microsoft.com/sample-solution-gallery/sample/pnp-microsoft-graph-connector&quot;&gt;Microsoft 365 Developer Proxy mock&lt;/a&gt; to simulate the API behavior. Using the mock you&apos;ll be able to easily test your code, without having to wait for the schema to be provisioned each time. It&apos;ll save you a lot of time!&lt;/p&gt;
  709. &lt;/blockquote&gt;
  710. &lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;POST https://graph.microsoft.com/v1.0/external/connections/contosohr/schema
  711. content-type: application/json
  712.  
  713. {
  714.  &amp;quot;baseType&amp;quot;: &amp;quot;microsoft.graph.externalItem&amp;quot;,
  715.  &amp;quot;properties&amp;quot;: [
  716.    {
  717.      &amp;quot;name&amp;quot;: &amp;quot;ticketTitle&amp;quot;,
  718.      &amp;quot;type&amp;quot;: &amp;quot;String&amp;quot;,
  719.      &amp;quot;isSearchable&amp;quot;: &amp;quot;true&amp;quot;,
  720.      &amp;quot;isRetrievable&amp;quot;: &amp;quot;true&amp;quot;,
  721.      &amp;quot;labels&amp;quot;: [
  722.        &amp;quot;title&amp;quot;
  723.      ]
  724.    },
  725.    {
  726.      &amp;quot;name&amp;quot;: &amp;quot;priority&amp;quot;,
  727.      &amp;quot;type&amp;quot;: &amp;quot;String&amp;quot;,
  728.      &amp;quot;isQueryable&amp;quot;: &amp;quot;true&amp;quot;,
  729.      &amp;quot;isRetrievable&amp;quot;: &amp;quot;true&amp;quot;,
  730.      &amp;quot;isSearchable&amp;quot;: &amp;quot;false&amp;quot;
  731.    },
  732.    {
  733.      &amp;quot;name&amp;quot;: &amp;quot;assignee&amp;quot;,
  734.      &amp;quot;type&amp;quot;: &amp;quot;String&amp;quot;,
  735.      &amp;quot;isRetrievable&amp;quot;: &amp;quot;true&amp;quot;
  736.    }
  737.  ]
  738. }
  739.  
  740. 202 Accepted
  741. Location: https://graph.microsoft.com/v1.0/external/connections/contosohr/operations/616bfeed-666f-4ce0-8cd9-058939010bfc
  742. &lt;/code&gt;&lt;/pre&gt;
  743. &lt;p&gt;Calling the URL gives you the current status of the creation operation:&lt;/p&gt;
  744. &lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;GET https://graph.microsoft.com/v1.0/external/connections/contosohr/operations/616bfeed-666f-4ce0-8cd9-058939010bfc
  745.  
  746. 200 OK
  747. content-type: application/json
  748.  
  749. {
  750.  &amp;quot;id&amp;quot;: &amp;quot;1.neu.0278337E599FC8DBF5607ED12CF463E4.6410CCF8F6DB8758539FB58EB56BF8DC&amp;quot;,
  751.  &amp;quot;status&amp;quot;: &amp;quot;inprogress&amp;quot;,
  752.  &amp;quot;error&amp;quot;: null
  753. }
  754. &lt;/code&gt;&lt;/pre&gt;
  755. &lt;p&gt;If you want to wait until the operation completes, you need to poll the URL at regular intervals, until the &lt;code&gt;status&lt;/code&gt; changes from &lt;code&gt;inprogress&lt;/code&gt; to either &lt;code&gt;completed&lt;/code&gt; or &lt;code&gt;failed&lt;/code&gt;.&lt;/p&gt;
  756. &lt;blockquote&gt;
  757. &lt;p&gt;TIP: For checking the status of creating Microsoft Graph connector schema, Microsoft recommends polling the status every 1 minute.&lt;/p&gt;
  758. &lt;/blockquote&gt;
  759. &lt;p&gt;As you can imagine, handling this flow adds a lot of code. A lot of code that makes it harder to understand what your app is doing exactly, but which you need nonetheless. Luckily, you don&apos;t need to put it in the main flow of your app. By implementing it as a middleware, you can make it readily available to any piece of your code that needs it without having to invoke it explicitly.&lt;/p&gt;
  760. &lt;h2&gt;What&apos;s middleware&lt;/h2&gt;
  761. &lt;p&gt;Microsoft Graph SDKs come with the concept of middleware. Middleware are pieces of code that handle outgoing requests and incoming responses. They&apos;re applied in a predefined order and run on every request you issue using the Microsoft Graph SDK.&lt;/p&gt;
  762. &lt;p&gt;Microsoft Graph SDKs ship with several middleware handlers and you can add your own to the pipeline too. What&apos;s neat about middleware, is that it&apos;s automatically applied to every request and doesn&apos;t clutter your main code, making it easier to follow.&lt;/p&gt;
  763. &lt;h2&gt;Handle long-running operations using middleware&lt;/h2&gt;
  764. &lt;p&gt;The following is code that you can use to handle the long-running operation of creating Microsoft Graph connector schema:&lt;/p&gt;
  765. &lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export function CompleteJobWithDelayMiddleware(delayMs) {
  766.  this.nextMiddleware = undefined;
  767.  this.execute = async (context) =&amp;gt; {
  768.    // wait for response
  769.    await this.nextMiddleware.execute(context);
  770.  
  771.    const location = context.response.headers.get(&apos;location&apos;);
  772.    if (location) {
  773.      console.debug(`Location: ${location}`);
  774.  
  775.      if (location.indexOf(&apos;/operations/&apos;) &amp;lt; 0) {
  776.        // not a job URL we should follow
  777.        return;
  778.      }
  779.  
  780.      console.log(`Waiting ${delayMs}ms before following location ${location}...`);
  781.      await new Promise(resolve =&amp;gt; setTimeout(resolve, delayMs));
  782.  
  783.      context.request = location;
  784.      context.options.method = &apos;GET&apos;;
  785.      context.options.body = undefined;
  786.      await this.execute(context);
  787.      return;
  788.    }
  789.  
  790.    if (context.request.indexOf(&apos;/operations/&apos;) &amp;lt; 0) {
  791.      // not a job
  792.      return;
  793.    }
  794.  
  795.    const res = context.response.clone();
  796.    if (!res.ok) {
  797.      console.debug(&apos;Response is not OK&apos;);
  798.      return;
  799.    }
  800.    const body = await res.json();
  801.    console.debug(`Status: ${body.status}`);
  802.    if (body.status === &apos;inprogress&apos;) {
  803.      console.debug(`Waiting ${delayMs}ms before trying again...`);
  804.      await new Promise(resolve =&amp;gt; setTimeout(resolve, delayMs));
  805.      await this.execute(context);
  806.    }
  807.  }
  808.  
  809.  return {
  810.    execute: async (context) =&amp;gt; {
  811.      return await this.execute(context);
  812.    },
  813.    setNext: (next) =&amp;gt; {
  814.      this.nextMiddleware = next;
  815.    }
  816.  }
  817. }
  818. &lt;/code&gt;&lt;/pre&gt;
  819. &lt;p&gt;The middleware starts with executing the request and capturing the response. Then, it checks if the response contains the &lt;code&gt;Location&lt;/code&gt; header.&lt;/p&gt;
  820. &lt;p&gt;If it does, it checks if the URL in the &lt;code&gt;Location&lt;/code&gt; header contains the &lt;code&gt;/operations/&lt;/code&gt; segment. Remember, the middleware runs on every request so before processing it, you need to be sure that you&apos;re looking at the right request/response.&lt;/p&gt;
  821. &lt;p&gt;If the URL in the &lt;code&gt;Location&lt;/code&gt; header doesn&apos;t contain the &lt;code&gt;/operations/&lt;/code&gt; segment, the middleware stops its execution.&lt;/p&gt;
  822. &lt;p&gt;If it does contain the &lt;code&gt;/operations/&lt;/code&gt; segment, the middleware will wait for the specified amount of time, and then call the URL from the &lt;code&gt;Location&lt;/code&gt; header, by passing it to the middleware chain.&lt;/p&gt;
  823. &lt;p&gt;Going back to the main flow, if the response didn&apos;t have a &lt;code&gt;Location&lt;/code&gt; header, and the request didn&apos;t have the &lt;code&gt;/operations/&lt;/code&gt; segment, it&apos;s some other request that&apos;s outside of the responsibility of this middleware, so the middleware skips its further processing.&lt;/p&gt;
  824. &lt;p&gt;If the request URL does contain the &lt;code&gt;/operations/&lt;/code&gt; segment, then the middleware reads the body to check the status of the operation. If the operation is in progress, the middleware will wait and call the status URL again. If the status is different, it will return the response to the caller.&lt;/p&gt;
  825. &lt;p&gt;It&apos;s a lot of code, isn&apos;t it? What&apos;s really cool about it though, is that because it&apos;s implemented in the middleware, it&apos;s invisible in your main code flow, where the only thing you&apos;ll see is:&lt;/p&gt;
  826. &lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const res = await client
  827.  .api(`/external/connections/${id}/schema`)
  828.  .header(&apos;content-type&apos;, &apos;application/json&apos;)
  829.  .post({
  830.    baseType: &apos;microsoft.graph.externalItem&apos;,
  831.    properties: schema
  832.  });
  833.  
  834. const status = res.status;
  835. if (status === &apos;completed&apos;) {
  836.  console.log(&apos;Schema created&apos;);
  837. }
  838. else {
  839.  console.error(`Schema creation failed: ${res.error.message}`);
  840. }
  841. &lt;/code&gt;&lt;/pre&gt;
  842. &lt;p&gt;See how simple to understand this code is? It&apos;s only possible, because of the complexity of handling the long-running operation has been put in a middleware.&lt;/p&gt;
  843. &lt;p&gt;To use this middleware, add it to the code where you instantiate &lt;code&gt;GraphClient&lt;/code&gt;:&lt;/p&gt;
  844. &lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const credential = new ClientSecretCredential(
  845.  appInfo.tenantId,
  846.  appInfo.appId,
  847.  appInfo.secrets[0].value
  848. );
  849.  
  850. const authProvider = new TokenCredentialAuthenticationProvider(credential, {
  851.  scopes: [&apos;https://graph.microsoft.com/.default&apos;],
  852. });
  853.  
  854. const middleware = MiddlewareFactory.getDefaultMiddlewareChain(authProvider);
  855. // add as a second middleware to get access to the access token
  856. middleware.splice(1, 0, new CompleteJobWithDelayMiddleware(60000));
  857.  
  858. export const client = Client.initWithMiddleware({ middleware });
  859. &lt;/code&gt;&lt;/pre&gt;
  860. &lt;p&gt;Because this middleware executes API calls, it&apos;s necessary that you add it after the auth middleware (so at least second in the chain) so that it gets access to the access token, or the API requests from the middleware will fail.&lt;/p&gt;
  861. &lt;h2&gt;Summary&lt;/h2&gt;
  862. &lt;p&gt;Using the Microsoft Graph JavaScript SDK is an easy way for you to get the data and insights from Microsoft 365 in your app. The SDK simplifies authentication and handles complex API scenarios for you. If you need to handle long-running operations, like creating Microsoft Graph connector schema, implement it as middleware to keep your main code flow clean and make the logic available throughout your app without repeating the code.&lt;/p&gt;
  863. </description><pubDate>Sat, 30 Sep 2023 14:24:43 GMT</pubDate></item><item><title>Configure proxy for .NET 4.x apps</title><link>https://blog.mastykarz.nl/configure-proxy-net-4-apps/</link><guid isPermaLink="true">https://blog.mastykarz.nl/configure-proxy-net-4-apps/</guid><description>&lt;p&gt;&lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/09/banner-netsh.png&quot; alt=&quot;Configure proxy for .NET 4.x apps&quot; class=&quot;webfeedsFeaturedVisual&quot; /&gt;&lt;/p&gt;&lt;p&gt;Here&apos;s how to configure proxy settings for your .NET 4.x app.&lt;/p&gt;
  864. &lt;p&gt;When you start the Microsoft 365 Developer Proxy on Windows, it just works. It automatically registers itself as a system proxy on your machine and intercepts web requests for the URLs specified in its configuration. However, if you start an app built with .NET 4.x, you&apos;ll see that the proxy is not picking up any of the app&apos;s requests.&lt;/p&gt;
  865. &lt;p&gt;&lt;picture&gt;
  866.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2023/09/proxy-net4x-no-requests.webp&quot; type=&quot;image/webp&quot;&gt;
  867.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/09/proxy-net4x-no-requests.png&quot; alt=&quot;Microsoft 365 Developer Proxy not picking up requests from a .NET 4.x app&quot;&gt;
  868. &lt;/picture&gt;&lt;/p&gt;
  869. &lt;p&gt;It turns out, that .NET 4.x uses WinHTTP API to implement proxy configuration. To configure a proxy so that it&apos;s being used by .NET 4.x apps, you can use the &lt;strong&gt;netsh&lt;/strong&gt; tool. For example, to enable a proxy running locally on port 8000, you&apos;d use:&lt;/p&gt;
  870. &lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;netsh winhttp set proxy proxy-server=&amp;quot;http=localhost:8000;https=localhost:8000&amp;quot;
  871. &lt;/code&gt;&lt;/pre&gt;
  872. &lt;p&gt;&lt;picture&gt;
  873.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2023/09/terminal-netsh.webp&quot; type=&quot;image/webp&quot;&gt;
  874.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/09/terminal-netsh.png&quot; alt=&quot;The netsh command to register a local proxy with the WinHTTP API in Windows Terminal&quot;&gt;
  875. &lt;/picture&gt;&lt;/p&gt;
  876. &lt;p&gt;After running this command, if you restart your .NET 4.x app, you&apos;ll see its requests intercepted by the proxy as expected.&lt;/p&gt;
  877. &lt;p&gt;&lt;picture&gt;
  878.  &lt;source srcset=&quot;https://blog.mastykarz.nl/assets/images/2023/09/proxy-net4x-requests.webp&quot; type=&quot;image/webp&quot;&gt;
  879.  &lt;img src=&quot;https://blog.mastykarz.nl/assets/images/2023/09/proxy-net4x-requests.png&quot; alt=&quot;Microsoft 365 Developer Proxy picking up requests from a .NET 4.x app&quot;&gt;
  880. &lt;/picture&gt;&lt;/p&gt;
  881. &lt;p&gt;To disable the proxy, reset the configuration using:&lt;/p&gt;
  882. &lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;netsh winhttp reset proxy
  883. &lt;/code&gt;&lt;/pre&gt;
  884. </description><pubDate>Sat, 30 Sep 2023 10:51:44 GMT</pubDate></item></channel></rss>

If you would like to create a banner that links to this page (i.e. this validation result), do the following:

  1. Download the "valid RSS" banner.

  2. Upload the image to your own server. (This step is important. Please do not link directly to the image on this server.)

  3. Add this HTML to your page (change the image src attribute if necessary):

If you would like to create a text link instead, here is the URL you can use:

http://www.feedvalidator.org/check.cgi?url=http%3A//feeds.feedburner.com/WaldekMastykarz

Copyright © 2002-9 Sam Ruby, Mark Pilgrim, Joseph Walton, and Phil Ringnalda