1
|
<!DOCTYPE html><html><head>
|
2
|
<title>astuce_dev</title>
|
3
|
<meta charset="utf-8">
|
4
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
5
|
|
6
|
<link rel="stylesheet" href="file:///c:\Users\mtoutant\.vscode\extensions\shd101wyy.markdown-preview-enhanced-0.6.8\node_modules\@shd101wyy\mume\dependencies\katex\katex.min.css">
|
7
|
|
8
|
|
9
|
|
10
|
|
11
|
|
12
|
|
13
|
|
14
|
|
15
|
|
16
|
<style>
|
17
|
|
18
|
|
19
|
|
20
|
|
21
|
code[class*="language-"],
|
22
|
pre[class*="language-"] {
|
23
|
color: #333;
|
24
|
background: none;
|
25
|
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
26
|
text-align: left;
|
27
|
white-space: pre;
|
28
|
word-spacing: normal;
|
29
|
word-break: normal;
|
30
|
word-wrap: normal;
|
31
|
line-height: 1.4;
|
32
|
|
33
|
-moz-tab-size: 8;
|
34
|
-o-tab-size: 8;
|
35
|
tab-size: 8;
|
36
|
|
37
|
-webkit-hyphens: none;
|
38
|
-moz-hyphens: none;
|
39
|
-ms-hyphens: none;
|
40
|
hyphens: none;
|
41
|
}
|
42
|
|
43
|
|
44
|
pre[class*="language-"] {
|
45
|
padding: .8em;
|
46
|
overflow: auto;
|
47
|
|
48
|
border-radius: 3px;
|
49
|
|
50
|
background: #f5f5f5;
|
51
|
}
|
52
|
|
53
|
|
54
|
:not(pre) > code[class*="language-"] {
|
55
|
padding: .1em;
|
56
|
border-radius: .3em;
|
57
|
white-space: normal;
|
58
|
background: #f5f5f5;
|
59
|
}
|
60
|
|
61
|
.token.comment,
|
62
|
.token.blockquote {
|
63
|
color: #969896;
|
64
|
}
|
65
|
|
66
|
.token.cdata {
|
67
|
color: #183691;
|
68
|
}
|
69
|
|
70
|
.token.doctype,
|
71
|
.token.punctuation,
|
72
|
.token.variable,
|
73
|
.token.macro.property {
|
74
|
color: #333;
|
75
|
}
|
76
|
|
77
|
.token.operator,
|
78
|
.token.important,
|
79
|
.token.keyword,
|
80
|
.token.rule,
|
81
|
.token.builtin {
|
82
|
color: #a71d5d;
|
83
|
}
|
84
|
|
85
|
.token.string,
|
86
|
.token.url,
|
87
|
.token.regex,
|
88
|
.token.attr-value {
|
89
|
color: #183691;
|
90
|
}
|
91
|
|
92
|
.token.property,
|
93
|
.token.number,
|
94
|
.token.boolean,
|
95
|
.token.entity,
|
96
|
.token.atrule,
|
97
|
.token.constant,
|
98
|
.token.symbol,
|
99
|
.token.command,
|
100
|
.token.code {
|
101
|
color: #0086b3;
|
102
|
}
|
103
|
|
104
|
.token.tag,
|
105
|
.token.selector,
|
106
|
.token.prolog {
|
107
|
color: #63a35c;
|
108
|
}
|
109
|
|
110
|
.token.function,
|
111
|
.token.namespace,
|
112
|
.token.pseudo-element,
|
113
|
.token.class,
|
114
|
.token.class-name,
|
115
|
.token.pseudo-class,
|
116
|
.token.id,
|
117
|
.token.url-reference .token.variable,
|
118
|
.token.attr-name {
|
119
|
color: #795da3;
|
120
|
}
|
121
|
|
122
|
.token.entity {
|
123
|
cursor: help;
|
124
|
}
|
125
|
|
126
|
.token.title,
|
127
|
.token.title .token.punctuation {
|
128
|
font-weight: bold;
|
129
|
color: #1d3e81;
|
130
|
}
|
131
|
|
132
|
.token.list {
|
133
|
color: #ed6a43;
|
134
|
}
|
135
|
|
136
|
.token.inserted {
|
137
|
background-color: #eaffea;
|
138
|
color: #55a532;
|
139
|
}
|
140
|
|
141
|
.token.deleted {
|
142
|
background-color: #ffecec;
|
143
|
color: #bd2c00;
|
144
|
}
|
145
|
|
146
|
.token.bold {
|
147
|
font-weight: bold;
|
148
|
}
|
149
|
|
150
|
.token.italic {
|
151
|
font-style: italic;
|
152
|
}
|
153
|
|
154
|
|
155
|
|
156
|
.language-json .token.property {
|
157
|
color: #183691;
|
158
|
}
|
159
|
|
160
|
.language-markup .token.tag .token.punctuation {
|
161
|
color: #333;
|
162
|
}
|
163
|
|
164
|
|
165
|
code.language-css,
|
166
|
.language-css .token.function {
|
167
|
color: #0086b3;
|
168
|
}
|
169
|
|
170
|
|
171
|
.language-yaml .token.atrule {
|
172
|
color: #63a35c;
|
173
|
}
|
174
|
|
175
|
code.language-yaml {
|
176
|
color: #183691;
|
177
|
}
|
178
|
|
179
|
|
180
|
.language-ruby .token.function {
|
181
|
color: #333;
|
182
|
}
|
183
|
|
184
|
|
185
|
.language-markdown .token.url {
|
186
|
color: #795da3;
|
187
|
}
|
188
|
|
189
|
|
190
|
.language-makefile .token.symbol {
|
191
|
color: #795da3;
|
192
|
}
|
193
|
|
194
|
.language-makefile .token.variable {
|
195
|
color: #183691;
|
196
|
}
|
197
|
|
198
|
.language-makefile .token.builtin {
|
199
|
color: #0086b3;
|
200
|
}
|
201
|
|
202
|
|
203
|
.language-bash .token.keyword {
|
204
|
color: #0086b3;
|
205
|
}
|
206
|
|
207
|
|
208
|
pre[data-line] {
|
209
|
position: relative;
|
210
|
padding: 1em 0 1em 3em;
|
211
|
}
|
212
|
pre[data-line] .line-highlight-wrapper {
|
213
|
position: absolute;
|
214
|
top: 0;
|
215
|
left: 0;
|
216
|
background-color: transparent;
|
217
|
display: block;
|
218
|
width: 100%;
|
219
|
}
|
220
|
|
221
|
pre[data-line] .line-highlight {
|
222
|
position: absolute;
|
223
|
left: 0;
|
224
|
right: 0;
|
225
|
padding: inherit 0;
|
226
|
margin-top: 1em;
|
227
|
background: hsla(24, 20%, 50%,.08);
|
228
|
background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0));
|
229
|
pointer-events: none;
|
230
|
line-height: inherit;
|
231
|
white-space: pre;
|
232
|
}
|
233
|
|
234
|
pre[data-line] .line-highlight:before,
|
235
|
pre[data-line] .line-highlight[data-end]:after {
|
236
|
content: attr(data-start);
|
237
|
position: absolute;
|
238
|
top: .4em;
|
239
|
left: .6em;
|
240
|
min-width: 1em;
|
241
|
padding: 0 .5em;
|
242
|
background-color: hsla(24, 20%, 50%,.4);
|
243
|
color: hsl(24, 20%, 95%);
|
244
|
font: bold 65%/1.5 sans-serif;
|
245
|
text-align: center;
|
246
|
vertical-align: .3em;
|
247
|
border-radius: 999px;
|
248
|
text-shadow: none;
|
249
|
box-shadow: 0 1px white;
|
250
|
}
|
251
|
|
252
|
pre[data-line] .line-highlight[data-end]:after {
|
253
|
content: attr(data-end);
|
254
|
top: auto;
|
255
|
bottom: .4em;
|
256
|
}html body{font-family:"Helvetica Neue",Helvetica,"Segoe UI",Arial,freesans,sans-serif;font-size:16px;line-height:1.6;color:#333;background-color:#fff;overflow:initial;box-sizing:border-box;word-wrap:break-word}html body>:first-child{margin-top:0}html body h1,html body h2,html body h3,html body h4,html body h5,html body h6{line-height:1.2;margin-top:1em;margin-bottom:16px;color:#000}html body h1{font-size:2.25em;font-weight:300;padding-bottom:.3em}html body h2{font-size:1.75em;font-weight:400;padding-bottom:.3em}html body h3{font-size:1.5em;font-weight:500}html body h4{font-size:1.25em;font-weight:600}html body h5{font-size:1.1em;font-weight:600}html body h6{font-size:1em;font-weight:600}html body h1,html body h2,html body h3,html body h4,html body h5{font-weight:600}html body h5{font-size:1em}html body h6{color:#5c5c5c}html body strong{color:#000}html body del{color:#5c5c5c}html body a:not([href]){color:inherit;text-decoration:none}html body a{color:#08c;text-decoration:none}html body a:hover{color:#00a3f5;text-decoration:none}html body img{max-width:100%}html body>p{margin-top:0;margin-bottom:16px;word-wrap:break-word}html body>ul,html body>ol{margin-bottom:16px}html body ul,html body ol{padding-left:2em}html body ul.no-list,html body ol.no-list{padding:0;list-style-type:none}html body ul ul,html body ul ol,html body ol ol,html body ol ul{margin-top:0;margin-bottom:0}html body li{margin-bottom:0}html body li.task-list-item{list-style:none}html body li>p{margin-top:0;margin-bottom:0}html body .task-list-item-checkbox{margin:0 .2em .25em -1.8em;vertical-align:middle}html body .task-list-item-checkbox:hover{cursor:pointer}html body blockquote{margin:16px 0;font-size:inherit;padding:0 15px;color:#5c5c5c;background-color:#f0f0f0;border-left:4px solid #d6d6d6}html body blockquote>:first-child{margin-top:0}html body blockquote>:last-child{margin-bottom:0}html body hr{height:4px;margin:32px 0;background-color:#d6d6d6;border:0 none}html body table{margin:10px 0 15px 0;border-collapse:collapse;border-spacing:0;display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all}html body table th{font-weight:bold;color:#000}html body table td,html body table th{border:1px solid #d6d6d6;padding:6px 13px}html body dl{padding:0}html body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:bold}html body dl dd{padding:0 16px;margin-bottom:16px}html body code{font-family:Menlo,Monaco,Consolas,'Courier New',monospace;font-size:.85em !important;color:#000;background-color:#f0f0f0;border-radius:3px;padding:.2em 0}html body code::before,html body code::after{letter-spacing:-0.2em;content:"\00a0"}html body pre>code{padding:0;margin:0;font-size:.85em !important;word-break:normal;white-space:pre;background:transparent;border:0}html body .highlight{margin-bottom:16px}html body .highlight pre,html body pre{padding:1em;overflow:auto;font-size:.85em !important;line-height:1.45;border:#d6d6d6;border-radius:3px}html body .highlight pre{margin-bottom:0;word-break:normal}html body pre code,html body pre tt{display:inline;max-width:initial;padding:0;margin:0;overflow:initial;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}html body pre code:before,html body pre tt:before,html body pre code:after,html body pre tt:after{content:normal}html body p,html body blockquote,html body ul,html body ol,html body dl,html body pre{margin-top:0;margin-bottom:16px}html body kbd{color:#000;border:1px solid #d6d6d6;border-bottom:2px solid #c7c7c7;padding:2px 4px;background-color:#f0f0f0;border-radius:3px}@media print{html body{background-color:#fff}html body h1,html body h2,html body h3,html body h4,html body h5,html body h6{color:#000;page-break-after:avoid}html body blockquote{color:#5c5c5c}html body pre{page-break-inside:avoid}html body table{display:table}html body img{display:block;max-width:100%;max-height:100%}html body pre,html body code{word-wrap:break-word;white-space:pre}}.markdown-preview{width:100%;height:100%;box-sizing:border-box}.markdown-preview .pagebreak,.markdown-preview .newpage{page-break-before:always}.markdown-preview pre.line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}.markdown-preview pre.line-numbers>code{position:relative}.markdown-preview pre.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:1em;font-size:100%;left:0;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.markdown-preview pre.line-numbers .line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.markdown-preview pre.line-numbers .line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.markdown-preview .mathjax-exps .MathJax_Display{text-align:center !important}.markdown-preview:not([for="preview"]) .code-chunk .btn-group{display:none}.markdown-preview:not([for="preview"]) .code-chunk .status{display:none}.markdown-preview:not([for="preview"]) .code-chunk .output-div{margin-bottom:16px}.markdown-preview .md-toc{padding:0}.markdown-preview .md-toc .md-toc-link-wrapper .md-toc-link{display:inline;padding:.25rem 0}.markdown-preview .md-toc .md-toc-link-wrapper .md-toc-link p,.markdown-preview .md-toc .md-toc-link-wrapper .md-toc-link div{display:inline}.markdown-preview .md-toc .md-toc-link-wrapper.highlighted .md-toc-link{font-weight:800}.scrollbar-style::-webkit-scrollbar{width:8px}.scrollbar-style::-webkit-scrollbar-track{border-radius:10px;background-color:transparent}.scrollbar-style::-webkit-scrollbar-thumb{border-radius:5px;background-color:rgba(150,150,150,0.66);border:4px solid rgba(150,150,150,0.66);background-clip:content-box}html body[for="html-export"]:not([data-presentation-mode]){position:relative;width:100%;height:100%;top:0;left:0;margin:0;padding:0;overflow:auto}html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview{position:relative;top:0}@media screen and (min-width:914px){html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview{padding:2em calc(50% - 457px + 2em)}}@media screen and (max-width:914px){html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview{padding:2em}}@media screen and (max-width:450px){html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview{font-size:14px !important;padding:1em}}@media print{html body[for="html-export"]:not([data-presentation-mode]) #sidebar-toc-btn{display:none}}html body[for="html-export"]:not([data-presentation-mode]) #sidebar-toc-btn{position:fixed;bottom:8px;left:8px;font-size:28px;cursor:pointer;color:inherit;z-index:99;width:32px;text-align:center;opacity:.4}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] #sidebar-toc-btn{opacity:1}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc{position:fixed;top:0;left:0;width:300px;height:100%;padding:32px 0 48px 0;font-size:14px;box-shadow:0 0 4px rgba(150,150,150,0.33);box-sizing:border-box;overflow:auto;background-color:inherit}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc::-webkit-scrollbar{width:8px}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc::-webkit-scrollbar-track{border-radius:10px;background-color:transparent}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc::-webkit-scrollbar-thumb{border-radius:5px;background-color:rgba(150,150,150,0.66);border:4px solid rgba(150,150,150,0.66);background-clip:content-box}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc a{text-decoration:none}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc{padding:0 16px}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper .md-toc-link{display:inline;padding:.25rem 0}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper .md-toc-link p,html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper .md-toc-link div{display:inline}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc .md-toc .md-toc-link-wrapper.highlighted .md-toc-link{font-weight:800}html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview{left:300px;width:calc(100% - 300px);padding:2em calc(50% - 457px - 300px/2);margin:0;box-sizing:border-box}@media screen and (max-width:1274px){html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview{padding:2em}}@media screen and (max-width:450px){html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview{width:100%}}html body[for="html-export"]:not([data-presentation-mode]):not([html-show-sidebar-toc]) .markdown-preview{left:50%;transform:translateX(-50%)}html body[for="html-export"]:not([data-presentation-mode]):not([html-show-sidebar-toc]) .md-sidebar-toc{display:none}
|
257
|
|
258
|
|
259
|
|
260
|
</style>
|
261
|
</head>
|
262
|
<body for="html-export">
|
263
|
<div class="mume markdown-preview ">
|
264
|
<h1 class="mume-header" id="astuces-de-developpement-maui">Astuces de developpement MAUI</h1>
|
265
|
|
266
|
<p>Cette documentation résume les astuces de développement des applications mobiles MAUI que j'ai apprises lors de mes developpements pour les applications <code>SicpaExpe</code> et <code>SIPMobile</code>. Elle est à destination des developpeurs initiés à la technologie MAUI, qui cherchent a approfondir leurs connaissances.</p>
|
267
|
<p>Si une information est floue, difficile à comprendre, ou qu'il manque des détails, n'hésitez pas à m'envoyer un mail à <code>martin.toutant1@gmail.com</code> et j'essaierai d'y répondre le plus rapidement possible.</p>
|
268
|
<h2 class="mume-header" id="sommaire">Sommaire</h2>
|
269
|
|
270
|
<ol>
|
271
|
<li>Utilisation des services</li>
|
272
|
<li>Gestion du mode déconnecté</li>
|
273
|
<li>Appel aux WS REST</li>
|
274
|
<li>Permissions spécifiques fichiers externes Android</li>
|
275
|
</ol>
|
276
|
<h2 class="mume-header" id="utilisation-des-services">Utilisation des services</h2>
|
277
|
|
278
|
<p>Les services en MAUI sont des classes instanciées une ou plusieurs fois et gardée en mémoire, et qui peuvent être récupérées et utilisées par les <code>ViewModels</code> par l'injection de dépendances.<br>
|
279
|
Plus d'infos sur :</p>
|
280
|
<ul>
|
281
|
<li><a href="https://learn.microsoft.com/en-us/dotnet/architecture/maui/dependency-injection">https://learn.microsoft.com/en-us/dotnet/architecture/maui/dependency-injection</a> (doc officielle Microsoft, anglais)</li>
|
282
|
<li><a href="https://www.e-naxos.com/Blog/post/un-projet-zero-maui-realiste-partie-3-5-modele-services-et-injection-de-dependances.asp">https://www.e-naxos.com/Blog/post/un-projet-zero-maui-realiste-partie-3-5-modele-services-et-injection-de-dependances.asp</a> (DotBlog, français)</li>
|
283
|
</ul>
|
284
|
<p>Dans les applications <code>SicpaExpe</code> et <code>SIPMobile</code> on retrouve des services communs aux deux applications.</p>
|
285
|
<ul>
|
286
|
<li>Les services de DAO <code>ServerDAO</code> et <code>LocalDAO</code> qui contiennent les fonctions d'accès aux données sur les différentes bases (voir section sur WS REST).</li>
|
287
|
<li>Le <code>ConnexionService</code> qui gère l'état de la connexion.</li>
|
288
|
<li>Le <code>LoadingIndicatorService</code> qui permet de gérer l'affichage de l'indicateur de chargement. Permet de n'avoir qu'une seule classe qui gère la propriété <code>IsBusy</code> plutot que de l'avoir dans chaque <code>ViewModel</code>. Permet aussi que des classes hors <code>ViewModels</code> puissent utiliser l'indicateur de chargement.</li>
|
289
|
<li>Le <code>SettingsService</code> qui permet de garder quelques variables globales. Par exemple l'unité actuelle dans l'application <code>SIPMobile</code></li>
|
290
|
<li>Un service de gestion d'envoi de données <code>MouvementService</code> sur <code>SIPMobile</code> et <code>DataSenderService</code> sur <code>SicpaExpe</code>. Cela permet de séparer et de factoriser les fonctions d'envoi des données que je laissais auparavant dans les <code>ViewModels</code>.</li>
|
291
|
</ul>
|
292
|
<p>De plus, MAUI recommande que chaque <code>View</code> et chaque <code>ViewModel</code> soient aussi enregistrés en tant que service. Cela permet de profiter de <strong>l'injection de dépendance</strong> en laissant l'application instancier les classes avec les services dont elles ont besoin.</p>
|
293
|
<p>Chaque service doit être enregistré dans le <code>MauiProgram</code>, point d'entrée du code de l'application. En étant enregistré, il peut ensuite être injecté dans chaque classe qui le demande.</p>
|
294
|
<p>Exemple dans l'application <code>SicpaExpe</code>:</p>
|
295
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-static">static</span> <span class="token return-type class-name">MauiApp</span> <span class="token function">CreateMauiApp</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
296
|
<span class="token punctuation">{</span>
|
297
|
<span class="token comment">//... </span>
|
298
|
|
299
|
<span class="token comment">// Enregistrement des services</span>
|
300
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>LocalDAO<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
301
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>ServerDAO<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
302
|
|
303
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>ConnexionService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
304
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>SettingsService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
305
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>DataSenderService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
306
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>LoadingIndicatorService<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
307
|
|
308
|
<span class="token comment">// Enregistrement des pages</span>
|
309
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>SelectionExpePage<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
310
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>ExpeMainPage<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
311
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation"><</span>SaisieVariablePage<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
312
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>SettingsPage<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
313
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation"><</span>OfflineDataPage<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
314
|
|
315
|
<span class="token comment">// Enregistrement des viewmodels</span>
|
316
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>SelectionExpeViewModel<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
317
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>ExpeMainViewModel<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
318
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation"><</span>SaisieVariableViewModel<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
319
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddSingleton</span><span class="token generic class-name"><span class="token punctuation"><</span>SettingsViewModel<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
320
|
builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation"><</span>OfflineDataViewModel<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
321
|
|
322
|
<span class="token comment">// ...</span>
|
323
|
|
324
|
<span class="token keyword keyword-return">return</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
325
|
<span class="token punctuation">}</span>
|
326
|
</pre><p>Exemple d'un <code>ViewModel</code> demandant six services différents :</p>
|
327
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">ExpeMainViewModel</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ViewModelBase</span><span class="token punctuation">,</span> <span class="token class-name">IRecipient<span class="token punctuation"><</span>ServerResultsLoadedMessage<span class="token punctuation">></span></span></span>
|
328
|
<span class="token punctuation">{</span>
|
329
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">SettingsService</span> settingsService<span class="token punctuation">;</span>
|
330
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">LocalDAO</span> localDAO<span class="token punctuation">;</span>
|
331
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">ServerDAO</span> serverDAO<span class="token punctuation">;</span>
|
332
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">ConnexionService</span> connService<span class="token punctuation">;</span>
|
333
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">DataSenderService</span> dataSenderService<span class="token punctuation">;</span>
|
334
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">LoadingIndicatorService</span> loadingIndicatorService<span class="token punctuation">;</span>
|
335
|
|
336
|
<span class="token comment">// ...</span>
|
337
|
|
338
|
<span class="token keyword keyword-public">public</span> <span class="token function">ExpeMainViewModel</span><span class="token punctuation">(</span>
|
339
|
<span class="token class-name">LocalDAO</span> localDAO<span class="token punctuation">,</span>
|
340
|
<span class="token class-name">ServerDAO</span> serverDAO<span class="token punctuation">,</span>
|
341
|
<span class="token class-name">SettingsService</span> settingsService<span class="token punctuation">,</span>
|
342
|
<span class="token class-name">ConnexionService</span> connService<span class="token punctuation">,</span>
|
343
|
<span class="token class-name">DataSenderService</span> dataSenderService<span class="token punctuation">,</span>
|
344
|
<span class="token class-name">LoadingIndicatorService</span> loadingIndicatorService<span class="token punctuation">)</span>
|
345
|
<span class="token punctuation">{</span>
|
346
|
<span class="token keyword keyword-this">this</span><span class="token punctuation">.</span>localDAO <span class="token operator">=</span> localDAO<span class="token punctuation">;</span>
|
347
|
<span class="token keyword keyword-this">this</span><span class="token punctuation">.</span>serverDAO <span class="token operator">=</span> serverDAO<span class="token punctuation">;</span>
|
348
|
<span class="token keyword keyword-this">this</span><span class="token punctuation">.</span>settingsService <span class="token operator">=</span> settingsService<span class="token punctuation">;</span>
|
349
|
<span class="token keyword keyword-this">this</span><span class="token punctuation">.</span>connService <span class="token operator">=</span> connService<span class="token punctuation">;</span>
|
350
|
<span class="token keyword keyword-this">this</span><span class="token punctuation">.</span>dataSenderService <span class="token operator">=</span> dataSenderService<span class="token punctuation">;</span>
|
351
|
<span class="token keyword keyword-this">this</span><span class="token punctuation">.</span>loadingIndicatorService <span class="token operator">=</span> loadingIndicatorService<span class="token punctuation">;</span>
|
352
|
<span class="token comment">// ...</span>
|
353
|
<span class="token punctuation">}</span>
|
354
|
|
355
|
<span class="token comment">// ...</span>
|
356
|
|
357
|
<span class="token punctuation">}</span>
|
358
|
</pre><p>Ce <code>ViewModel</code> est instancié par l'application, et l'application lui <strong>injecte</strong> tous les services donnés en arguments. Le <em>Binding</em> du <code>ViewModel</code> à la <code>Page</code> se fait ainsi de la manière suivante :</p>
|
359
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token function">ExpeMainPage</span><span class="token punctuation">(</span><span class="token class-name">ExpeMainViewModel</span> vm<span class="token punctuation">)</span>
|
360
|
<span class="token punctuation">{</span>
|
361
|
<span class="token function">InitializeComponent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
362
|
BindingContext <span class="token operator">=</span> vm<span class="token punctuation">;</span>
|
363
|
<span class="token comment">// ...</span>
|
364
|
<span class="token punctuation">}</span>
|
365
|
</pre><p>Comme le <code>ViewModel</code> est enregisté en tant que service, il sera aussi injecté dans le constructeur de la page, au moment de l'instanciation.</p>
|
366
|
<p>NB: Dans le <code>MauiProgram.cs</code> chaque Service peut être enregistré comme <code>Singleton</code> ou <code>Transient</code>:</p>
|
367
|
<ul>
|
368
|
<li>En tant que <code>Singleton</code>, il ne sera instancié qu'une seule fois et c'est cette seule et même instance qui sera utilisée à chaque fois quelle sera demandée.</li>
|
369
|
<li>En tant que <code>Transient</code>, il sera instancié à chaque fois qu'il sera demandé.</li>
|
370
|
</ul>
|
371
|
<p>A garder en tête lorsqu'on débug ! 😉</p>
|
372
|
<h2 class="mume-header" id="gestion-du-mode-d%C3%A9connect%C3%A9">Gestion du mode déconnecté</h2>
|
373
|
|
374
|
<p>À la suite du hackhaton MAUI qui a eu lieu fin mars, j'ai (re-)réfléchi sur la mise en place du mode déconnecté dans les applications SicpaExpe puis SIPMobile.</p>
|
375
|
<p>Dans cette section, je décris l'utilisation d'une base de donner locale SQLite en utilisant la librairie <code>sqlite-net-pcl</code>, recommandé par la documentation officielle Microsoft. Plus d'informations sur <a href="https://learn.microsoft.com/en-us/dotnet/maui/data-cloud/database-sqlite?view=net-maui-7.0">https://learn.microsoft.com/en-us/dotnet/maui/data-cloud/database-sqlite?view=net-maui-7.0</a></p>
|
376
|
<p>Pour des souci de simplicité et pour éviter les problèmes de désynchronisation je me suis fixé les règles de développement suivantes :</p>
|
377
|
<ul>
|
378
|
<li>Toujours aller chercher les données dans la base de données locale SQLite</li>
|
379
|
<li>Minimiser le nombre d'objets stockés comme variable globales</li>
|
380
|
<li>Synchroniser avec le serveur seulement lorsque la connexion est établie</li>
|
381
|
</ul>
|
382
|
<p>Cela donne le workflow suivant lors de l'enregistrement d'un résultat sur <code>SicpaExpe</code>, par exemple.</p>
|
383
|
<p><img src="data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLSBEbyBub3QgZWRpdCB0aGlzIGZpbGUgd2l0aCBlZGl0b3JzIG90aGVyIHRoYW4gZGlhZ3JhbXMubmV0IC0tPgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZlcnNpb249IjEuMSIgd2lkdGg9IjM2MXB4IiBoZWlnaHQ9IjM2MXB4IiB2aWV3Qm94PSItMC41IC0wLjUgMzYxIDM2MSIgY29udGVudD0iJmx0O214ZmlsZSBob3N0PSZxdW90O0VsZWN0cm9uJnF1b3Q7IG1vZGlmaWVkPSZxdW90OzIwMjMtMDYtMTZUMDk6NDM6MDAuMDQ0WiZxdW90OyBhZ2VudD0mcXVvdDs1LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgZHJhdy5pby8xOS4wLjMgQ2hyb21lLzEwMi4wLjUwMDUuNjMgRWxlY3Ryb24vMTkuMC4zIFNhZmFyaS81MzcuMzYmcXVvdDsgZXRhZz0mcXVvdDtmVEljTEpSUEJIM3ZXclRpbEFmbCZxdW90OyB2ZXJzaW9uPSZxdW90OzE5LjAuMyZxdW90OyB0eXBlPSZxdW90O2RldmljZSZxdW90OyZndDsmbHQ7ZGlhZ3JhbSBpZD0mcXVvdDtOTndsV1hEQUVWZkJXOFh1MW9WRSZxdW90OyBuYW1lPSZxdW90O1BhZ2UtMSZxdW90OyZndDs3Vm5iY3Rvd0VQMGFUOXVITUw1akhvRkEybWt5MHhtbVNmb29zTERWeWhZankxenk5VjNaTWtaeFRHaWFRR2JDaXlPdFZxdkwyYlBhRFlZelROWlhIQzNpR3haaWF0aG11RGFjUzhPMkxkTUo0SStVYkVxSjcvVktRY1JKcUpScXdZUTg0R3Fta3VZa3hKbW1LQmlqZ2l4MDRZeWxLWjRKVFlZNFp5dGRiYzZvdnVvQ1JiZ2htTXdRYlVydlNDamlVdG96elZyK0ZaTW9ybGIycTVFRVZjcEtrTVVvWktzZGtUTXluQ0ZuVEpTdFpEM0VWRjVlZFMvbHZISEw2SFpqSEtmaWtBbmpoSXZSWGZmMmUvOE85L2lmcTBuQzdpOTh0VGV4cVE2TVF6aS82akl1WWhheEZORlJMUjF3bHFjaGxsWk42TlU2MTR3dFFHaUI4RGNXWXFQQVJMbGdJSXBGUXRVb2JKaHY3dVg4amxkMWZ5bHpSZWR5cmZVMnFqZG5xUmlqaEZBcCtEbk5VNUhEM205WXl0VGdoT1Y4SnRlTWhRRC9zRDJuRHgrNEVmbVJDbGtuWWl5aUdDMUkxcG14cEJpWVpZWHFlRjZhaHViV3VHY1BkczJYQjdJQXVrRjViL0t5V3VGUW9xemFWQnNHbFZzakhtR3hSOC9aT2cyd0RiTUV3OTNBUEk0cEVtU3A3d01wdDQrMmVyVm5RRU01eHo4NGlySzdSRFJYSzQxU2ppT1NDWTRUZVdTZytLYzhsZnN4aG83UjcyVTVGVWcwM0V0M25sVk1CSjRzVUhFL0s0Z2d1cU84UDhDWG1BdTgzZzk1RTZKcWdxOUN3ZVpSZjFWSEZxc0tGL0ZPVkhITk4wTFZPd1g5M3grcXIwSmo1MEFhdXllbHNkM2c4UmpsNnlmZDRCcE40VEhYb0VPVVJDbTBaM0E3bUlOQThvSEFhOWxYQXdrSnc5SkxjRVllMExTd0ovMWt3UWpnSm8xN0E4TzdmQUtLdlc3UnlqNzE3cXVWNnRkMkY4RjIxMitsNm9YWk1idVdxOUZWWGQzQlFDbmpQK1RKYTh1MnB4bTlzSFVEYkQ3UHNHamd2TjNoeTZIdm5ibCtkSzRISitXNjArRDZMVWZrQTNLOTl4elhmYTlydlFxN0xmOVlkRzZDTzFRbFVKRitHYzY0bVh2RkxKbm0yY2ZMdXdJOTc5cm1VenQ1VjNETXRLc3FiczlsMXdsanVIdGdETytlTW9hN0RaWi9TME1Jd2dMblhKWmNXSDZBOXlYcFpRL0xHZ3dKSWFPMllmc1VqamFZZ3E0ZnlkYm5DUlJsRUFJZ2RFcHJwdldsR1NVK2NJWG02b0hDc1E4czBJSzNpaFRkQnY2TnNyc0FuREo0bU05UTdpbTIzZURVeGJabE5RQTZaK0F2amQ3L20xbTNGR2QyaTlOVUpzclhRczJxL2FIUE9kcnNxS2tNdUhVZDUxR2djZDFILzd0OWJsKzZQalRLSGJ4cWhoazhFWHVXak1oalNoZktNRjhXcjlCemI0eDVmbU4yQTVNZEhPK1JnVzc5QTBQcEYvWFBOTTdvTHc9PSZsdDsvZGlhZ3JhbSZndDsmbHQ7L214ZmlsZSZndDsiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjU1LCAyNTUsIDI1NSk7Ij48ZGVmcz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPkBpbXBvcnQgdXJsKGh0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzP2ZhbWlseT1SZWQrSGF0K01vbm8pOyYjeGE7QGltcG9ydCB1cmwoaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVVidW50dStNb25vKTsmI3hhOzwvc3R5bGU+PC9kZWZzPjxnPjxwYXRoIGQ9Ik0gMTgwIDQwIEwgMTgwIDczLjYzIiBmaWxsPSJub25lIiBzdHJva2U9InJnYigwLCAwLCAwKSIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludGVyLWV2ZW50cz0ic3Ryb2tlIi8+PHBhdGggZD0iTSAxODAgNzguODggTCAxNzYuNSA3MS44OCBMIDE4MCA3My42MyBMIDE4My41IDcxLjg4IFoiIGZpbGw9InJnYigwLCAwLCAwKSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxyZWN0IHg9IjEyMCIgeT0iMCIgd2lkdGg9IjEyMCIgaGVpZ2h0PSI0MCIgZmlsbD0icmdiKDI1NSwgMjU1LCAyNTUpIiBzdHJva2U9InJnYigwLCAwLCAwKSIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjUgLTAuNSkiPjxzd2l0Y2g+PGZvcmVpZ25PYmplY3QgcG9pbnRlci1ldmVudHM9Im5vbmUiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHJlcXVpcmVkRmVhdHVyZXM9Imh0dHA6Ly93d3cudzMub3JnL1RSL1NWRzExL2ZlYXR1cmUjRXh0ZW5zaWJpbGl0eSIgc3R5bGU9Im92ZXJmbG93OiB2aXNpYmxlOyB0ZXh0LWFsaWduOiBsZWZ0OyI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IGZsZXg7IGFsaWduLWl0ZW1zOiB1bnNhZmUgY2VudGVyOyBqdXN0aWZ5LWNvbnRlbnQ6IHVuc2FmZSBjZW50ZXI7IHdpZHRoOiAxMThweDsgaGVpZ2h0OiAxcHg7IHBhZGRpbmctdG9wOiAyMHB4OyBtYXJnaW4tbGVmdDogMTIxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IHdoaXRlLXNwYWNlOiBub3JtYWw7IG92ZXJmbG93LXdyYXA6IG5vcm1hbDsiPkVucmVnaXN0cmVtZW50IGQndW4gcsOpc3VsdGF0PC9kaXY+PC9kaXY+PC9kaXY+PC9mb3JlaWduT2JqZWN0Pjx0ZXh0IHg9IjE4MCIgeT0iMjQiIGZpbGw9InJnYigwLCAwLCAwKSIgZm9udC1mYW1pbHk9IlVidW50dSBNb25vIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkVucmVnaXN0cmVtZW50IGQndW4uLi48L3RleHQ+PC9zd2l0Y2g+PC9nPjxwYXRoIGQ9Ik0gMTQwIDEwMCBMIDYwIDEwMCBMIDYwIDE1My42MyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9InN0cm9rZSIvPjxwYXRoIGQ9Ik0gNjAgMTU4Ljg4IEwgNTYuNSAxNTEuODggTCA2MCAxNTMuNjMgTCA2My41IDE1MS44OCBaIiBmaWxsPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJhbGwiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC41IC0wLjUpIj48c3dpdGNoPjxmb3JlaWduT2JqZWN0IHBvaW50ZXItZXZlbnRzPSJub25lIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiByZXF1aXJlZEZlYXR1cmVzPSJodHRwOi8vd3d3LnczLm9yZy9UUi9TVkcxMS9mZWF0dXJlI0V4dGVuc2liaWxpdHkiIHN0eWxlPSJvdmVyZmxvdzogdmlzaWJsZTsgdGV4dC1hbGlnbjogbGVmdDsiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiBmbGV4OyBhbGlnbi1pdGVtczogdW5zYWZlIGNlbnRlcjsganVzdGlmeS1jb250ZW50OiB1bnNhZmUgY2VudGVyOyB3aWR0aDogMXB4OyBoZWlnaHQ6IDFweDsgcGFkZGluZy10b3A6IDEwMXB4OyBtYXJnaW4tbGVmdDogMTAxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgYmFja2dyb3VuZC1jb2xvcjogcmdiKDI1NSwgMjU1LCAyNTUpOyAiIHN0eWxlPSJib3gtc2l6aW5nOiBib3JkZXItYm94OyBmb250LXNpemU6IDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PGRpdiBzdHlsZT0iZGlzcGxheTogaW5saW5lLWJsb2NrOyBmb250LXNpemU6IDEycHg7IGZvbnQtZmFtaWx5OiAmcXVvdDtVYnVudHUgTW9ubyZxdW90OzsgY29sb3I6IHJnYigwLCAwLCAwKTsgbGluZS1oZWlnaHQ6IDEuMjsgcG9pbnRlci1ldmVudHM6IGFsbDsgYmFja2dyb3VuZC1jb2xvcjogcmdiKDI1NSwgMjU1LCAyNTUpOyB3aGl0ZS1zcGFjZTogbm93cmFwOyI+RmF1eDwvZGl2PjwvZGl2PjwvZGl2PjwvZm9yZWlnbk9iamVjdD48dGV4dCB4PSIxMDEiIHk9IjEwNCIgZmlsbD0icmdiKDAsIDAsIDApIiBmb250LWZhbWlseT0iVWJ1bnR1IE1vbm8iIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RmF1eDwvdGV4dD48L3N3aXRjaD48L2c+PHBhdGggZD0iTSAyMjAgMTAwIEwgMzAwIDEwMCBMIDMwMCAxNTMuNjMiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJzdHJva2UiLz48cGF0aCBkPSJNIDMwMCAxNTguODggTCAyOTYuNSAxNTEuODggTCAzMDAgMTUzLjYzIEwgMzAzLjUgMTUxLjg4IFoiIGZpbGw9InJnYigwLCAwLCAwKSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjUgLTAuNSkiPjxzd2l0Y2g+PGZvcmVpZ25PYmplY3QgcG9pbnRlci1ldmVudHM9Im5vbmUiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHJlcXVpcmVkRmVhdHVyZXM9Imh0dHA6Ly93d3cudzMub3JnL1RSL1NWRzExL2ZlYXR1cmUjRXh0ZW5zaWJpbGl0eSIgc3R5bGU9Im92ZXJmbG93OiB2aXNpYmxlOyB0ZXh0LWFsaWduOiBsZWZ0OyI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IGZsZXg7IGFsaWduLWl0ZW1zOiB1bnNhZmUgY2VudGVyOyBqdXN0aWZ5LWNvbnRlbnQ6IHVuc2FmZSBjZW50ZXI7IHdpZHRoOiAxcHg7IGhlaWdodDogMXB4OyBwYWRkaW5nLXRvcDogMTAxcHg7IG1hcmdpbi1sZWZ0OiAyNjFweDsiPjxkaXYgZGF0YS1kcmF3aW8tY29sb3JzPSJjb2xvcjogcmdiKDAsIDAsIDApOyBiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjU1LCAyNTUsIDI1NSk7ICIgc3R5bGU9ImJveC1zaXppbmc6IGJvcmRlci1ib3g7IGZvbnQtc2l6ZTogMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48ZGl2IHN0eWxlPSJkaXNwbGF5OiBpbmxpbmUtYmxvY2s7IGZvbnQtc2l6ZTogMTJweDsgZm9udC1mYW1pbHk6ICZxdW90O1VidW50dSBNb25vJnF1b3Q7OyBjb2xvcjogcmdiKDAsIDAsIDApOyBsaW5lLWhlaWdodDogMS4yOyBwb2ludGVyLWV2ZW50czogYWxsOyBiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjU1LCAyNTUsIDI1NSk7IHdoaXRlLXNwYWNlOiBub3dyYXA7Ij5WcmFpPC9kaXY+PC9kaXY+PC9kaXY+PC9mb3JlaWduT2JqZWN0Pjx0ZXh0IHg9IjI2MSIgeT0iMTA0IiBmaWxsPSJyZ2IoMCwgMCwgMCkiIGZvbnQtZmFtaWx5PSJVYnVudHUgTW9ubyIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5WcmFpPC90ZXh0Pjwvc3dpdGNoPjwvZz48cGF0aCBkPSJNIDE4MCA4MCBMIDIyMCAxMDAgTCAxODAgMTIwIEwgMTQwIDEwMCBaIiBmaWxsPSJyZ2IoMjU1LCAyNTUsIDI1NSkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJhbGwiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC41IC0wLjUpIj48c3dpdGNoPjxmb3JlaWduT2JqZWN0IHBvaW50ZXItZXZlbnRzPSJub25lIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiByZXF1aXJlZEZlYXR1cmVzPSJodHRwOi8vd3d3LnczLm9yZy9UUi9TVkcxMS9mZWF0dXJlI0V4dGVuc2liaWxpdHkiIHN0eWxlPSJvdmVyZmxvdzogdmlzaWJsZTsgdGV4dC1hbGlnbjogbGVmdDsiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiBmbGV4OyBhbGlnbi1pdGVtczogdW5zYWZlIGNlbnRlcjsganVzdGlmeS1jb250ZW50OiB1bnNhZmUgY2VudGVyOyB3aWR0aDogNzhweDsgaGVpZ2h0OiAxcHg7IHBhZGRpbmctdG9wOiAxMDBweDsgbWFyZ2luLWxlZnQ6IDE0MXB4OyI+PGRpdiBkYXRhLWRyYXdpby1jb2xvcnM9ImNvbG9yOiByZ2IoMCwgMCwgMCk7ICIgc3R5bGU9ImJveC1zaXppbmc6IGJvcmRlci1ib3g7IGZvbnQtc2l6ZTogMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48ZGl2IHN0eWxlPSJkaXNwbGF5OiBpbmxpbmUtYmxvY2s7IGZvbnQtc2l6ZTogMTJweDsgZm9udC1mYW1pbHk6ICZxdW90O1VidW50dSBNb25vJnF1b3Q7OyBjb2xvcjogcmdiKDAsIDAsIDApOyBsaW5lLWhlaWdodDogMS4yOyBwb2ludGVyLWV2ZW50czogYWxsOyB3aGl0ZS1zcGFjZTogbm9ybWFsOyBvdmVyZmxvdy13cmFwOiBub3JtYWw7Ij5Db25uZWN0w6k/PC9kaXY+PC9kaXY+PC9kaXY+PC9mb3JlaWduT2JqZWN0Pjx0ZXh0IHg9IjE4MCIgeT0iMTA0IiBmaWxsPSJyZ2IoMCwgMCwgMCkiIGZvbnQtZmFtaWx5PSJVYnVudHUgTW9ubyIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5Db25uZWN0w6k/PC90ZXh0Pjwvc3dpdGNoPjwvZz48cGF0aCBkPSJNIDYwIDI0MCBMIDYwIDI4MCBMIDE4MCAyODAgTCAxODAgMzEzLjYzIiBmaWxsPSJub25lIiBzdHJva2U9InJnYigwLCAwLCAwKSIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludGVyLWV2ZW50cz0ic3Ryb2tlIi8+PHBhdGggZD0iTSAxODAgMzE4Ljg4IEwgMTc2LjUgMzExLjg4IEwgMTgwIDMxMy42MyBMIDE4My41IDMxMS44OCBaIiBmaWxsPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJhbGwiLz48cmVjdCB4PSIwIiB5PSIxNjAiIHdpZHRoPSIxMjAiIGhlaWdodD0iODAiIGZpbGw9InJnYigyNTUsIDI1NSwgMjU1KSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHBvaW50ZXItZXZlbnRzPSJhbGwiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC41IC0wLjUpIj48c3dpdGNoPjxmb3JlaWduT2JqZWN0IHBvaW50ZXItZXZlbnRzPSJub25lIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiByZXF1aXJlZEZlYXR1cmVzPSJodHRwOi8vd3d3LnczLm9yZy9UUi9TVkcxMS9mZWF0dXJlI0V4dGVuc2liaWxpdHkiIHN0eWxlPSJvdmVyZmxvdzogdmlzaWJsZTsgdGV4dC1hbGlnbjogbGVmdDsiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiBmbGV4OyBhbGlnbi1pdGVtczogdW5zYWZlIGNlbnRlcjsganVzdGlmeS1jb250ZW50OiB1bnNhZmUgY2VudGVyOyB3aWR0aDogMTE4cHg7IGhlaWdodDogMXB4OyBwYWRkaW5nLXRvcDogMjAwcHg7IG1hcmdpbi1sZWZ0OiAxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IHdoaXRlLXNwYWNlOiBub3JtYWw7IG92ZXJmbG93LXdyYXA6IG5vcm1hbDsiPkluZGljYXRldXIgZGUgZG9ubsOpZSBlbiBhdHRlbnRlPGJyIC8+KFN0YXR1cyA9IDEpPC9kaXY+PC9kaXY+PC9kaXY+PC9mb3JlaWduT2JqZWN0Pjx0ZXh0IHg9IjYwIiB5PSIyMDQiIGZpbGw9InJnYigwLCAwLCAwKSIgZm9udC1mYW1pbHk9IlVidW50dSBNb25vIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkluZGljYXRldXIgZGUgZG9ubsOpZS4uLjwvdGV4dD48L3N3aXRjaD48L2c+PHJlY3QgeD0iMTIwIiB5PSIzMjAiIHdpZHRoPSIxMjAiIGhlaWdodD0iNDAiIGZpbGw9InJnYigyNTUsIDI1NSwgMjU1KSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHBvaW50ZXItZXZlbnRzPSJhbGwiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC41IC0wLjUpIj48c3dpdGNoPjxmb3JlaWduT2JqZWN0IHBvaW50ZXItZXZlbnRzPSJub25lIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiByZXF1aXJlZEZlYXR1cmVzPSJodHRwOi8vd3d3LnczLm9yZy9UUi9TVkcxMS9mZWF0dXJlI0V4dGVuc2liaWxpdHkiIHN0eWxlPSJvdmVyZmxvdzogdmlzaWJsZTsgdGV4dC1hbGlnbjogbGVmdDsiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiBmbGV4OyBhbGlnbi1pdGVtczogdW5zYWZlIGNlbnRlcjsganVzdGlmeS1jb250ZW50OiB1bnNhZmUgY2VudGVyOyB3aWR0aDogMTE4cHg7IGhlaWdodDogMXB4OyBwYWRkaW5nLXRvcDogMzQwcHg7IG1hcmdpbi1sZWZ0OiAxMjFweDsiPjxkaXYgZGF0YS1kcmF3aW8tY29sb3JzPSJjb2xvcjogcmdiKDAsIDAsIDApOyAiIHN0eWxlPSJib3gtc2l6aW5nOiBib3JkZXItYm94OyBmb250LXNpemU6IDBweDsgdGV4dC1hbGlnbjogY2VudGVyOyI+PGRpdiBzdHlsZT0iZGlzcGxheTogaW5saW5lLWJsb2NrOyBmb250LXNpemU6IDEycHg7IGZvbnQtZmFtaWx5OiAmcXVvdDtVYnVudHUgTW9ubyZxdW90OzsgY29sb3I6IHJnYigwLCAwLCAwKTsgbGluZS1oZWlnaHQ6IDEuMjsgcG9pbnRlci1ldmVudHM6IGFsbDsgd2hpdGUtc3BhY2U6IG5vcm1hbDsgb3ZlcmZsb3ctd3JhcDogbm9ybWFsOyI+RW5yZWdpc3RyZW1lbnQgZW4gbG9jYWw8L2Rpdj48L2Rpdj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PHRleHQgeD0iMTgwIiB5PSIzNDQiIGZpbGw9InJnYigwLCAwLCAwKSIgZm9udC1mYW1pbHk9IlVidW50dSBNb25vIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkVucmVnaXN0cmVtZW50IGVuIGxvLi4uPC90ZXh0Pjwvc3dpdGNoPjwvZz48cGF0aCBkPSJNIDMwMCAyNDAgTCAzMDAgMjgwIEwgMTgwIDI4MCBMIDE4MCAzMTMuNjMiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJzdHJva2UiLz48cGF0aCBkPSJNIDE4MCAzMTguODggTCAxNzYuNSAzMTEuODggTCAxODAgMzEzLjYzIEwgMTgzLjUgMzExLjg4IFoiIGZpbGw9InJnYigwLCAwLCAwKSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxyZWN0IHg9IjI0MCIgeT0iMTYwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjgwIiBmaWxsPSJyZ2IoMjU1LCAyNTUsIDI1NSkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBwb2ludGVyLWV2ZW50cz0iYWxsIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTAuNSAtMC41KSI+PHN3aXRjaD48Zm9yZWlnbk9iamVjdCBwb2ludGVyLWV2ZW50cz0ibm9uZSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgcmVxdWlyZWRGZWF0dXJlcz0iaHR0cDovL3d3dy53My5vcmcvVFIvU1ZHMTEvZmVhdHVyZSNFeHRlbnNpYmlsaXR5IiBzdHlsZT0ib3ZlcmZsb3c6IHZpc2libGU7IHRleHQtYWxpZ246IGxlZnQ7Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogZmxleDsgYWxpZ24taXRlbXM6IHVuc2FmZSBjZW50ZXI7IGp1c3RpZnktY29udGVudDogdW5zYWZlIGNlbnRlcjsgd2lkdGg6IDExOHB4OyBoZWlnaHQ6IDFweDsgcGFkZGluZy10b3A6IDIwMHB4OyBtYXJnaW4tbGVmdDogMjQxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IHdoaXRlLXNwYWNlOiBub3JtYWw7IG92ZXJmbG93LXdyYXA6IG5vcm1hbDsiPkVudm9pIGF1IHNlcnZldXI8YnIgLz4oU3RhdHVzID0gMCk8L2Rpdj48L2Rpdj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PHRleHQgeD0iMzAwIiB5PSIyMDQiIGZpbGw9InJnYigwLCAwLCAwKSIgZm9udC1mYW1pbHk9IlVidW50dSBNb25vIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPkVudm9pIGF1IHNlcnZldXIuLi48L3RleHQ+PC9zd2l0Y2g+PC9nPjwvZz48c3dpdGNoPjxnIHJlcXVpcmVkRmVhdHVyZXM9Imh0dHA6Ly93d3cudzMub3JnL1RSL1NWRzExL2ZlYXR1cmUjRXh0ZW5zaWJpbGl0eSIvPjxhIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAsLTUpIiB4bGluazpocmVmPSJodHRwczovL3d3dy5kaWFncmFtcy5uZXQvZG9jL2ZhcS9zdmctZXhwb3J0LXRleHQtcHJvYmxlbXMiIHRhcmdldD0iX2JsYW5rIj48dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LXNpemU9IjEwcHgiIHg9IjUwJSIgeT0iMTAwJSI+VGV4dCBpcyBub3QgU1ZHIC0gY2Fubm90IGRpc3BsYXk8L3RleHQ+PC9hPjwvc3dpdGNoPjwvc3ZnPg==" alt="graph mode deco"></p>
|
384
|
<p>Pour synchroniser les données, au lancement de l'application et lors du rétablissement de la connexion, l'application vérifie s'il existe des données en attente d'envoi (i.e. <code>Status == 1</code>) et les envoie. Puis toutes les données locales sont supprimées (fonction <code>ClearDataFromTable<T></code>) et ensuite re-téléchargées depuis le serveur.</p>
|
385
|
<p><img src="data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLSBEbyBub3QgZWRpdCB0aGlzIGZpbGUgd2l0aCBlZGl0b3JzIG90aGVyIHRoYW4gZGlhZ3JhbXMubmV0IC0tPgo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHZlcnNpb249IjEuMSIgd2lkdGg9IjI2MnB4IiBoZWlnaHQ9IjM4MnB4IiB2aWV3Qm94PSItMC41IC0wLjUgMjYyIDM4MiIgY29udGVudD0iJmx0O214ZmlsZSBob3N0PSZxdW90O0VsZWN0cm9uJnF1b3Q7IG1vZGlmaWVkPSZxdW90OzIwMjMtMDYtMTZUMDk6NTY6MTkuMTY5WiZxdW90OyBhZ2VudD0mcXVvdDs1LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgZHJhdy5pby8xOS4wLjMgQ2hyb21lLzEwMi4wLjUwMDUuNjMgRWxlY3Ryb24vMTkuMC4zIFNhZmFyaS81MzcuMzYmcXVvdDsgZXRhZz0mcXVvdDs0SEFpWFZXdC14NFo0REE2dF9HMSZxdW90OyB2ZXJzaW9uPSZxdW90OzE5LjAuMyZxdW90OyB0eXBlPSZxdW90O2RldmljZSZxdW90OyZndDsmbHQ7ZGlhZ3JhbSBpZD0mcXVvdDtQaHdFbG5qOFVYczE1czZ4bkljbSZxdW90OyBuYW1lPSZxdW90O1BhZ2UtMSZxdW90OyZndDs3VmxkazlvMkZQMDFucllQeS9nRGI5YVBDeXpkZG5ZN21hRnAya2RoQzFzWjI5ZVJaY0Q1OWIyeVpCdWp3Skx1RWpvVFhrQTZrcTZrZSs2NWtzRHlwdG4yVjA2SzVCa2ltbHF1SFcwdGIyYTVidURjNGFjRWFnWDQ0MEFCTVdlUmdwd2VXTEF2VklPMlJpc1cwWExRVVFDa2doVkRNSVE4cDZFWVlJUnoyQXk3clNBZHpscVFtQnJBSWlTcGlYNWtrVWowdG15N3h4OHBpNU4yNXR1MkpTTnRadzJVQ1lsZ3N3TjVENVkzNVFCQ2xiTHRsS2JTZDYxZjFMajVnZFp1WVp6bTRwUUJqK0Z6L2ZseCt1SDNQejU5Zkh4YS96WmZCUG5OTzcwMlViY2JwaEh1WDFlQml3Uml5RW42MEtNVERsVWVVV25WeGxyZjV3bWdRTkJCOEJNVm90Wmtra29BUW9uSVV0MjZnbHpNU2NaU0dSTWZsbFV1S2x6RE0rU2dHeGRROFZDT1RZUkFubDNmdThjUDNKbjhrQjNLVVF3UXA1UVVyQnlGa0RVTllkbDBuYStVYVN4MnhuMTNvczJyL2NwTkhuU2poc3AyRVlkODE0WWo0VEVWUi9xNUhka29FZ29aRlJ4WFozT2FFc0hXdzNVUUhhNXgxNjluRkF1YTFHOGdXTnRkazdUU015M3FQRXc0NUt6RTZTRTNBbUJJN3laaGdpNEswbmhpZ3hMLy8xRFptRmN4NXJnZHRXdktCZDBlSjlja1k5dkpWdzJwOStxYlh2dE9LK2hrUi9kaiswejgzUm44WFJYN254WHJucWhZNzVXS2JZYmVjMDdxblE0Rk1IVERqdVgzRXVoano5dUxQVGZ3OTZKSFdleGpxVnZhSzlLRFo4VFhYNXl3cjhiWUUxbmkyVDZJQzVLeU9NZHlpRlJRam9CVUg4UFQ4MTQzWkN5S1ZBalNrbjBoeThhZURFTHREVFR1VHl4LzlsS01IZFMyUHZlMTVmNjAzUTJQdzhJNm1BanMwVHZIY1laOHFOcTNCWUhCOG8zdkRxemVPSHVaQTFhckVvTnpQMis4QWRmQkpUSUhrc0Rydi9YNHB2S1BySXo4dGpyYjdqYk9hbDI3Wk1acFZ2S2Vjb1llbDFIZExPZ0NhV2g4aVRUVUtVZ0g2Tmo5SG1uSU5kTFFuRlRiSHlBTkJVZlQwSTA5R3R0M2I1bUd6cDltVENvdDl6WVYyclh5RFVhME9tNC9WL0x4TXhsNnVZT3hGTXZ2R2ZyV21ucldmWUFQUVpSZ0xyY2hoT1JhUGl4L292a2E4TVJDT3VmdFRFdmVEdjU1SVlpb3lxWjUxc3hxTzcrWTNWckVVcm1qaC9jdnh3bGtTelQzNHNYNERKZlI3cEw1MG1YVTJUOVQzdTQxWVJzZXVkNCtUMDM3M25kSys2OWkyTHdQUG1oNVJZMzZvcUVZVTVBL2xjZ1NTSFFTbU5MNmlnQk5XZjNBYjA3UEhzcmNjMCtVdVg4Mmxac0VYVlYrcXNySEo2cmN2NlRLeDRiS0YxVlI0T1dvYkg0UlFxM0xMVUFsbE1pUEt2OHE1WU5TSHR1WGxySnZFUDJuNGpCVlgyRWk0elNqemNYc0JOWkx5dGUwNGxmV2o3RWVuSTkxclBiL0dLZ2JlLyszaS9md0x3PT0mbHQ7L2RpYWdyYW0mZ3Q7Jmx0Oy9teGZpbGUmZ3Q7IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjogcmdiKDI1NSwgMjU1LCAyNTUpOyI+PGRlZnM+PHN0eWxlIHR5cGU9InRleHQvY3NzIj5AaW1wb3J0IHVybChodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9VWJ1bnR1K01vbm8pOyYjeGE7QGltcG9ydCB1cmwoaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVJlZCtIYXQrTW9ubyk7JiN4YTs8L3N0eWxlPjwvZGVmcz48Zz48cGF0aCBkPSJNIDYwIDQwIEwgNjAgNzMuNjMiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJzdHJva2UiLz48cGF0aCBkPSJNIDYwIDc4Ljg4IEwgNTYuNSA3MS44OCBMIDYwIDczLjYzIEwgNjMuNSA3MS44OCBaIiBmaWxsPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJhbGwiLz48cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjQwIiBmaWxsPSJyZ2IoMjU1LCAyNTUsIDI1NSkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBwb2ludGVyLWV2ZW50cz0iYWxsIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTAuNSAtMC41KSI+PHN3aXRjaD48Zm9yZWlnbk9iamVjdCBwb2ludGVyLWV2ZW50cz0ibm9uZSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgcmVxdWlyZWRGZWF0dXJlcz0iaHR0cDovL3d3dy53My5vcmcvVFIvU1ZHMTEvZmVhdHVyZSNFeHRlbnNpYmlsaXR5IiBzdHlsZT0ib3ZlcmZsb3c6IHZpc2libGU7IHRleHQtYWxpZ246IGxlZnQ7Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogZmxleDsgYWxpZ24taXRlbXM6IHVuc2FmZSBjZW50ZXI7IGp1c3RpZnktY29udGVudDogdW5zYWZlIGNlbnRlcjsgd2lkdGg6IDExOHB4OyBoZWlnaHQ6IDFweDsgcGFkZGluZy10b3A6IDIwcHg7IG1hcmdpbi1sZWZ0OiAxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IHdoaXRlLXNwYWNlOiBub3JtYWw7IG92ZXJmbG93LXdyYXA6IG5vcm1hbDsiPlN5bmNocm9uaXNhdGlvbjwvZGl2PjwvZGl2PjwvZGl2PjwvZm9yZWlnbk9iamVjdD48dGV4dCB4PSI2MCIgeT0iMjQiIGZpbGw9InJnYigwLCAwLCAwKSIgZm9udC1mYW1pbHk9IlVidW50dSBNb25vIiBmb250LXNpemU9IjEycHgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlN5bmNocm9uaXNhdGlvbjwvdGV4dD48L3N3aXRjaD48L2c+PHBhdGggZD0iTSAxMjAgMTM1IEwgMjAwIDEzNSBMIDIwMCAxNTMuNjMiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJzdHJva2UiLz48cGF0aCBkPSJNIDIwMCAxNTguODggTCAxOTYuNSAxNTEuODggTCAyMDAgMTUzLjYzIEwgMjAzLjUgMTUxLjg4IFoiIGZpbGw9InJnYigwLCAwLCAwKSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjUgLTAuNSkiPjxzd2l0Y2g+PGZvcmVpZ25PYmplY3QgcG9pbnRlci1ldmVudHM9Im5vbmUiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHJlcXVpcmVkRmVhdHVyZXM9Imh0dHA6Ly93d3cudzMub3JnL1RSL1NWRzExL2ZlYXR1cmUjRXh0ZW5zaWJpbGl0eSIgc3R5bGU9Im92ZXJmbG93OiB2aXNpYmxlOyB0ZXh0LWFsaWduOiBsZWZ0OyI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IGZsZXg7IGFsaWduLWl0ZW1zOiB1bnNhZmUgY2VudGVyOyBqdXN0aWZ5LWNvbnRlbnQ6IHVuc2FmZSBjZW50ZXI7IHdpZHRoOiAxcHg7IGhlaWdodDogMXB4OyBwYWRkaW5nLXRvcDogMTM1cHg7IG1hcmdpbi1sZWZ0OiAxNTBweDsiPjxkaXYgZGF0YS1kcmF3aW8tY29sb3JzPSJjb2xvcjogcmdiKDAsIDAsIDApOyBiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjU1LCAyNTUsIDI1NSk7ICIgc3R5bGU9ImJveC1zaXppbmc6IGJvcmRlci1ib3g7IGZvbnQtc2l6ZTogMHB4OyB0ZXh0LWFsaWduOiBjZW50ZXI7Ij48ZGl2IHN0eWxlPSJkaXNwbGF5OiBpbmxpbmUtYmxvY2s7IGZvbnQtc2l6ZTogMTFweDsgZm9udC1mYW1pbHk6ICZxdW90O1VidW50dSBNb25vJnF1b3Q7OyBjb2xvcjogcmdiKDAsIDAsIDApOyBsaW5lLWhlaWdodDogMS4yOyBwb2ludGVyLWV2ZW50czogYWxsOyBiYWNrZ3JvdW5kLWNvbG9yOiByZ2IoMjU1LCAyNTUsIDI1NSk7IHdoaXRlLXNwYWNlOiBub3dyYXA7Ij5WcmFpPC9kaXY+PC9kaXY+PC9kaXY+PC9mb3JlaWduT2JqZWN0Pjx0ZXh0IHg9IjE1MCIgeT0iMTM5IiBmaWxsPSJyZ2IoMCwgMCwgMCkiIGZvbnQtZmFtaWx5PSJVYnVudHUgTW9ubyIgZm9udC1zaXplPSIxMXB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5WcmFpPC90ZXh0Pjwvc3dpdGNoPjwvZz48cGF0aCBkPSJNIDYwIDE5MCBMIDYwIDI2NSBMIDEzMy42MyAyNjUiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJzdHJva2UiLz48cGF0aCBkPSJNIDEzOC44OCAyNjUgTCAxMzEuODggMjY4LjUgTCAxMzMuNjMgMjY1IEwgMTMxLjg4IDI2MS41IFoiIGZpbGw9InJnYigwLCAwLCAwKSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjUgLTAuNSkiPjxzd2l0Y2g+PGZvcmVpZ25PYmplY3QgcG9pbnRlci1ldmVudHM9Im5vbmUiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIHJlcXVpcmVkRmVhdHVyZXM9Imh0dHA6Ly93d3cudzMub3JnL1RSL1NWRzExL2ZlYXR1cmUjRXh0ZW5zaWJpbGl0eSIgc3R5bGU9Im92ZXJmbG93OiB2aXNpYmxlOyB0ZXh0LWFsaWduOiBsZWZ0OyI+PGRpdiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgc3R5bGU9ImRpc3BsYXk6IGZsZXg7IGFsaWduLWl0ZW1zOiB1bnNhZmUgY2VudGVyOyBqdXN0aWZ5LWNvbnRlbnQ6IHVuc2FmZSBjZW50ZXI7IHdpZHRoOiAxcHg7IGhlaWdodDogMXB4OyBwYWRkaW5nLXRvcDogMjM2cHg7IG1hcmdpbi1sZWZ0OiA2MnB4OyI+PGRpdiBkYXRhLWRyYXdpby1jb2xvcnM9ImNvbG9yOiByZ2IoMCwgMCwgMCk7IGJhY2tncm91bmQtY29sb3I6IHJnYigyNTUsIDI1NSwgMjU1KTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMXB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IGJhY2tncm91bmQtY29sb3I6IHJnYigyNTUsIDI1NSwgMjU1KTsgd2hpdGUtc3BhY2U6IG5vd3JhcDsiPkZhdXg8L2Rpdj48L2Rpdj48L2Rpdj48L2ZvcmVpZ25PYmplY3Q+PHRleHQgeD0iNjIiIHk9IjI0MCIgZmlsbD0icmdiKDAsIDAsIDApIiBmb250LWZhbWlseT0iVWJ1bnR1IE1vbm8iIGZvbnQtc2l6ZT0iMTFweCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RmF1eDwvdGV4dD48L3N3aXRjaD48L2c+PHBhdGggZD0iTSA2MCA4MCBMIDEyMCAxMzUgTCA2MCAxOTAgTCAwIDEzNSBaIiBmaWxsPSJyZ2IoMjU1LCAyNTUsIDI1NSkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJhbGwiLz48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC41IC0wLjUpIj48c3dpdGNoPjxmb3JlaWduT2JqZWN0IHBvaW50ZXItZXZlbnRzPSJub25lIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiByZXF1aXJlZEZlYXR1cmVzPSJodHRwOi8vd3d3LnczLm9yZy9UUi9TVkcxMS9mZWF0dXJlI0V4dGVuc2liaWxpdHkiIHN0eWxlPSJvdmVyZmxvdzogdmlzaWJsZTsgdGV4dC1hbGlnbjogbGVmdDsiPjxkaXYgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGh0bWwiIHN0eWxlPSJkaXNwbGF5OiBmbGV4OyBhbGlnbi1pdGVtczogdW5zYWZlIGNlbnRlcjsganVzdGlmeS1jb250ZW50OiB1bnNhZmUgY2VudGVyOyB3aWR0aDogMTE4cHg7IGhlaWdodDogMXB4OyBwYWRkaW5nLXRvcDogMTM1cHg7IG1hcmdpbi1sZWZ0OiAxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogSGVsdmV0aWNhOyBjb2xvcjogcmdiKDAsIDAsIDApOyBsaW5lLWhlaWdodDogMS4yOyBwb2ludGVyLWV2ZW50czogYWxsOyB3aGl0ZS1zcGFjZTogbm9ybWFsOyBvdmVyZmxvdy13cmFwOiBub3JtYWw7Ij48Zm9udCBmYWNlPSJVYnVudHUgTW9ubyI+RG9ubsOpZXMgZW4gYXR0ZW50ZSBkJ2Vudm9pID88YnIgLz4oU3RhdHVzID09IDEpPGJyIC8+PC9mb250PjwvZGl2PjwvZGl2PjwvZGl2PjwvZm9yZWlnbk9iamVjdD48dGV4dCB4PSI2MCIgeT0iMTM5IiBmaWxsPSJyZ2IoMCwgMCwgMCkiIGZvbnQtZmFtaWx5PSJIZWx2ZXRpY2EiIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+RG9ubsOpZXMgZW4gYXR0ZW50ZSBkLi4uPC90ZXh0Pjwvc3dpdGNoPjwvZz48cGF0aCBkPSJNIDIwMCAyMTAgTCAyMDAgMjMwIEwgMjAwIDIyMCBMIDIwMCAyMzMuNjMiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJzdHJva2UiLz48cGF0aCBkPSJNIDIwMCAyMzguODggTCAxOTYuNSAyMzEuODggTCAyMDAgMjMzLjYzIEwgMjAzLjUgMjMxLjg4IFoiIGZpbGw9InJnYigwLCAwLCAwKSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxyZWN0IHg9IjE0MCIgeT0iMTYwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjUwIiBmaWxsPSJyZ2IoMjU1LCAyNTUsIDI1NSkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBwb2ludGVyLWV2ZW50cz0iYWxsIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTAuNSAtMC41KSI+PHN3aXRjaD48Zm9yZWlnbk9iamVjdCBwb2ludGVyLWV2ZW50cz0ibm9uZSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgcmVxdWlyZWRGZWF0dXJlcz0iaHR0cDovL3d3dy53My5vcmcvVFIvU1ZHMTEvZmVhdHVyZSNFeHRlbnNpYmlsaXR5IiBzdHlsZT0ib3ZlcmZsb3c6IHZpc2libGU7IHRleHQtYWxpZ246IGxlZnQ7Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogZmxleDsgYWxpZ24taXRlbXM6IHVuc2FmZSBjZW50ZXI7IGp1c3RpZnktY29udGVudDogdW5zYWZlIGNlbnRlcjsgd2lkdGg6IDExOHB4OyBoZWlnaHQ6IDFweDsgcGFkZGluZy10b3A6IDE4NXB4OyBtYXJnaW4tbGVmdDogMTQxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IHdoaXRlLXNwYWNlOiBub3JtYWw7IG92ZXJmbG93LXdyYXA6IG5vcm1hbDsiPkVudm9pIGRlcyBkb25uw6llcyBsb2NhbGVzIG/DuTxiciAvPlN0YXR1cyA9PSAxPC9kaXY+PC9kaXY+PC9kaXY+PC9mb3JlaWduT2JqZWN0Pjx0ZXh0IHg9IjIwMCIgeT0iMTg5IiBmaWxsPSJyZ2IoMCwgMCwgMCkiIGZvbnQtZmFtaWx5PSJVYnVudHUgTW9ubyIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5FbnZvaSBkZXMgZG9ubsOpZXMgbG8uLi48L3RleHQ+PC9zd2l0Y2g+PC9nPjxwYXRoIGQ9Ik0gMjAwIDI5MCBMIDIwMCAzMjMuNjMiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50ZXItZXZlbnRzPSJzdHJva2UiLz48cGF0aCBkPSJNIDIwMCAzMjguODggTCAxOTYuNSAzMjEuODggTCAyMDAgMzIzLjYzIEwgMjAzLjUgMzIxLjg4IFoiIGZpbGw9InJnYigwLCAwLCAwKSIgc3Ryb2tlPSJyZ2IoMCwgMCwgMCkiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgcG9pbnRlci1ldmVudHM9ImFsbCIvPjxyZWN0IHg9IjE0MCIgeT0iMjQwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjUwIiBmaWxsPSJyZ2IoMjU1LCAyNTUsIDI1NSkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBwb2ludGVyLWV2ZW50cz0iYWxsIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTAuNSAtMC41KSI+PHN3aXRjaD48Zm9yZWlnbk9iamVjdCBwb2ludGVyLWV2ZW50cz0ibm9uZSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgcmVxdWlyZWRGZWF0dXJlcz0iaHR0cDovL3d3dy53My5vcmcvVFIvU1ZHMTEvZmVhdHVyZSNFeHRlbnNpYmlsaXR5IiBzdHlsZT0ib3ZlcmZsb3c6IHZpc2libGU7IHRleHQtYWxpZ246IGxlZnQ7Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogZmxleDsgYWxpZ24taXRlbXM6IHVuc2FmZSBjZW50ZXI7IGp1c3RpZnktY29udGVudDogdW5zYWZlIGNlbnRlcjsgd2lkdGg6IDExOHB4OyBoZWlnaHQ6IDFweDsgcGFkZGluZy10b3A6IDI2NXB4OyBtYXJnaW4tbGVmdDogMTQxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IHdoaXRlLXNwYWNlOiBub3JtYWw7IG92ZXJmbG93LXdyYXA6IG5vcm1hbDsiPlN1cHByZXNzaW9uIGRlIHRvdXRlcyBsZXMgZG9ubsOpZXMgbG9jYWxlczwvZGl2PjwvZGl2PjwvZGl2PjwvZm9yZWlnbk9iamVjdD48dGV4dCB4PSIyMDAiIHk9IjI2OSIgZmlsbD0icmdiKDAsIDAsIDApIiBmb250LWZhbWlseT0iVWJ1bnR1IE1vbm8iIGZvbnQtc2l6ZT0iMTJweCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+U3VwcHJlc3Npb24gZGUgdG91dGUuLi48L3RleHQ+PC9zd2l0Y2g+PC9nPjxyZWN0IHg9IjE0MCIgeT0iMzMwIiB3aWR0aD0iMTIwIiBoZWlnaHQ9IjUwIiBmaWxsPSJyZ2IoMjU1LCAyNTUsIDI1NSkiIHN0cm9rZT0icmdiKDAsIDAsIDApIiBwb2ludGVyLWV2ZW50cz0iYWxsIi8+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTAuNSAtMC41KSI+PHN3aXRjaD48Zm9yZWlnbk9iamVjdCBwb2ludGVyLWV2ZW50cz0ibm9uZSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgcmVxdWlyZWRGZWF0dXJlcz0iaHR0cDovL3d3dy53My5vcmcvVFIvU1ZHMTEvZmVhdHVyZSNFeHRlbnNpYmlsaXR5IiBzdHlsZT0ib3ZlcmZsb3c6IHZpc2libGU7IHRleHQtYWxpZ246IGxlZnQ7Ij48ZGl2IHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hodG1sIiBzdHlsZT0iZGlzcGxheTogZmxleDsgYWxpZ24taXRlbXM6IHVuc2FmZSBjZW50ZXI7IGp1c3RpZnktY29udGVudDogdW5zYWZlIGNlbnRlcjsgd2lkdGg6IDExOHB4OyBoZWlnaHQ6IDFweDsgcGFkZGluZy10b3A6IDM1NXB4OyBtYXJnaW4tbGVmdDogMTQxcHg7Ij48ZGl2IGRhdGEtZHJhd2lvLWNvbG9ycz0iY29sb3I6IHJnYigwLCAwLCAwKTsgIiBzdHlsZT0iYm94LXNpemluZzogYm9yZGVyLWJveDsgZm9udC1zaXplOiAwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsiPjxkaXYgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsgZm9udC1zaXplOiAxMnB4OyBmb250LWZhbWlseTogJnF1b3Q7VWJ1bnR1IE1vbm8mcXVvdDs7IGNvbG9yOiByZ2IoMCwgMCwgMCk7IGxpbmUtaGVpZ2h0OiAxLjI7IHBvaW50ZXItZXZlbnRzOiBhbGw7IHdoaXRlLXNwYWNlOiBub3JtYWw7IG92ZXJmbG93LXdyYXA6IG5vcm1hbDsiPlTDqWzDqWNoYXJnZW1lbnQgZGUgdG91dGVzIGxlcyBkb25uw6llcyBzZXJ2ZXVyPC9kaXY+PC9kaXY+PC9kaXY+PC9mb3JlaWduT2JqZWN0Pjx0ZXh0IHg9IjIwMCIgeT0iMzU5IiBmaWxsPSJyZ2IoMCwgMCwgMCkiIGZvbnQtZmFtaWx5PSJVYnVudHUgTW9ubyIgZm9udC1zaXplPSIxMnB4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj5Uw6lsw6ljaGFyZ2VtZW50IGRlIHRvLi4uPC90ZXh0Pjwvc3dpdGNoPjwvZz48L2c+PHN3aXRjaD48ZyByZXF1aXJlZEZlYXR1cmVzPSJodHRwOi8vd3d3LnczLm9yZy9UUi9TVkcxMS9mZWF0dXJlI0V4dGVuc2liaWxpdHkiLz48YSB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLC01KSIgeGxpbms6aHJlZj0iaHR0cHM6Ly93d3cuZGlhZ3JhbXMubmV0L2RvYy9mYXEvc3ZnLWV4cG9ydC10ZXh0LXByb2JsZW1zIiB0YXJnZXQ9Il9ibGFuayI+PHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxMHB4IiB4PSI1MCUiIHk9IjEwMCUiPlRleHQgaXMgbm90IFNWRyAtIGNhbm5vdCBkaXNwbGF5PC90ZXh0PjwvYT48L3N3aXRjaD48L3N2Zz4=" alt="graph synchro"></p>
|
386
|
<p>Exemple de fonction de récupération des résultats en attente d'envoi dans la base SQLite de l'application <code>SicpaExpe</code> :</p>
|
387
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-async">async</span> <span class="token return-type class-name">Task<span class="token punctuation"><</span>List<span class="token punctuation"><</span>Resultat<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">GetToSendResultats</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
388
|
<span class="token punctuation">{</span>
|
389
|
<span class="token keyword keyword-await">await</span> <span class="token function">Init</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
390
|
<span class="token keyword keyword-return">return</span>
|
391
|
<span class="token keyword keyword-await">await</span> conn<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Table</span><span class="token generic class-name"><span class="token punctuation"><</span>Resultat<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
392
|
<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>r <span class="token operator">=></span> r<span class="token punctuation">.</span>Status <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">)</span>
|
393
|
<span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
394
|
<span class="token punctuation">}</span>
|
395
|
</pre><p>Exemple de resynchronisation des données dans <code>SicpaExpe</code> (et de l'utilisation du <code>LoadingIndicatorService</code>):</p>
|
396
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token comment">// Récupération des résultats locaux</span>
|
397
|
<span class="token class-name">List<span class="token punctuation"><</span>Resultat<span class="token punctuation">></span></span> toSendResults <span class="token operator">=</span> <span class="token keyword keyword-await">await</span> localDAO<span class="token punctuation">.</span><span class="token function">GetToSendResultats</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
398
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>toSendResults<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
|
399
|
<span class="token punctuation">{</span>
|
400
|
loadingIndicatorService<span class="token punctuation">.</span>LoadingMessage <span class="token operator">=</span> <span class="token string">"Envoi des résultats locaux..."</span><span class="token punctuation">;</span>
|
401
|
<span class="token keyword keyword-await">await</span> <span class="token function">SendResultatsToServerAsync</span><span class="token punctuation">(</span>toSendResults<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
402
|
<span class="token punctuation">}</span>
|
403
|
|
404
|
loadingIndicatorService<span class="token punctuation">.</span>LoadingMessage <span class="token operator">=</span> <span class="token string">"Sauvegarde des résultats de la base..."</span><span class="token punctuation">;</span>
|
405
|
<span class="token class-name">List<span class="token punctuation"><</span>Resultat<span class="token punctuation">></span></span> allResults <span class="token operator">=</span> <span class="token keyword keyword-await">await</span> serverDAO<span class="token punctuation">.</span><span class="token function">GetAllResultats</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
406
|
<span class="token keyword keyword-await">await</span> localDAO<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ClearDataFromTableAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>Resultat<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
407
|
<span class="token keyword keyword-await">await</span> localDAO<span class="token punctuation">.</span><span class="token function">SaveItems</span><span class="token punctuation">(</span>allResults<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
408
|
|
409
|
<span class="token class-name">List<span class="token punctuation"><</span><span class="token keyword keyword-int">int</span><span class="token punctuation">></span></span> concernedAnimals <span class="token operator">=</span> allResults<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>r <span class="token operator">=></span> r<span class="token punctuation">.</span>AnimalID<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
410
|
WeakReferenceMessenger<span class="token punctuation">.</span>Default<span class="token punctuation">.</span><span class="token function">Send</span><span class="token punctuation">(</span><span class="token keyword keyword-new">new</span> <span class="token constructor-invocation class-name">ServerResultsLoadedMessage</span><span class="token punctuation">(</span>concernedAnimals<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
411
|
Toast<span class="token punctuation">.</span><span class="token function">Make</span><span class="token punctuation">(</span><span class="token string">"Synchronisation des résultats avec le serveur effectuée."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
412
|
loadingIndicatorService<span class="token punctuation">.</span>IsBusy <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
|
413
|
</pre><p>Dans l'application <code>SicpaExpe</code>, les données sont synchronisées systématiquement à chaque reconnexion, en utilisant l'évènement <code>Connectivity.ConnectivityChanged</code> et l'outil Messagerie de la librairie <code>MVVM Toolkit</code> de microsoft.<br>
|
414
|
Plus d'infos sur :</p>
|
415
|
<ul>
|
416
|
<li><a href="https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger">https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/messenger</a> (doc officielle Microsoft, anglais)</li>
|
417
|
<li><a href="https://www.e-naxos.com/Blog/post/Microsoft-MVVM-Toolkit-Partie-5.aspx">https://www.e-naxos.com/Blog/post/Microsoft-MVVM-Toolkit-Partie-5.aspx</a> (DotBlog, français)</li>
|
418
|
</ul>
|
419
|
<p>Un exemple de code tiré de l'application <code>SicpaExpe</code> :</p>
|
420
|
<p>Dans l'implémentation du <code>ConnexionService</code> :</p>
|
421
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">ConnexionService</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">INotifyPropertyChanged</span><span class="token punctuation">,</span> <span class="token class-name">IRecipient<span class="token punctuation"><</span>ConnectionSuccessMessage<span class="token punctuation">></span></span></span>
|
422
|
<span class="token punctuation">{</span>
|
423
|
<span class="token comment">// ...</span>
|
424
|
<span class="token keyword keyword-public">public</span> <span class="token keyword keyword-async">async</span> <span class="token return-type class-name">Task</span> <span class="token function">SetIsConnected</span><span class="token punctuation">(</span><span class="token class-name">ConnectivityChangedEventArgs</span> e <span class="token punctuation">)</span>
|
425
|
<span class="token punctuation">{</span>
|
426
|
<span class="token class-name"><span class="token keyword keyword-bool">bool</span></span> internetAccess <span class="token operator">=</span> e<span class="token punctuation">.</span>NetworkAccess <span class="token operator">==</span> NetworkAccess<span class="token punctuation">.</span>Internet<span class="token punctuation">;</span>
|
427
|
|
428
|
<span class="token comment">// Test de l'accès a la BDD wi-fi</span>
|
429
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>internetAccess<span class="token punctuation">)</span>
|
430
|
<span class="token punctuation">{</span>
|
431
|
<span class="token keyword keyword-try">try</span>
|
432
|
<span class="token punctuation">{</span>
|
433
|
<span class="token keyword keyword-await">await</span> Task<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span>
|
434
|
IsReseauLocal <span class="token operator">=</span>
|
435
|
<span class="token keyword keyword-new">new</span> <span class="token constructor-invocation class-name">Ping</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Send</span><span class="token punctuation">(</span>settingsService<span class="token punctuation">.</span>ServerInfo<span class="token punctuation">.</span>Address<span class="token punctuation">)</span><span class="token punctuation">.</span>Status <span class="token operator">==</span> IPStatus<span class="token punctuation">.</span>Success
|
436
|
<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
437
|
<span class="token punctuation">}</span>
|
438
|
<span class="token comment">// Cas ou VPN : une exception peut être levée par le ping</span>
|
439
|
<span class="token keyword keyword-catch">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span><span class="token punctuation">)</span>
|
440
|
<span class="token punctuation">{</span>
|
441
|
IsReseauLocal <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
|
442
|
<span class="token punctuation">}</span>
|
443
|
<span class="token punctuation">}</span>
|
444
|
<span class="token keyword keyword-else">else</span>
|
445
|
<span class="token punctuation">{</span>
|
446
|
IsReseauLocal <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
|
447
|
<span class="token punctuation">}</span>
|
448
|
<span class="token comment">// Envoi d'un message que la connexion a changé</span>
|
449
|
WeakReferenceMessenger<span class="token punctuation">.</span>Default<span class="token punctuation">.</span><span class="token function">Send</span><span class="token punctuation">(</span><span class="token keyword keyword-new">new</span> <span class="token constructor-invocation class-name">ConnectionChangedMessage</span><span class="token punctuation">(</span>IsReseauLocal<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
450
|
<span class="token punctuation">}</span>
|
451
|
|
452
|
<span class="token keyword keyword-public">public</span> <span class="token function">ConnexionService</span><span class="token punctuation">(</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">)</span>
|
453
|
<span class="token punctuation">{</span>
|
454
|
<span class="token comment">// ...</span>
|
455
|
Connectivity<span class="token punctuation">.</span>ConnectivityChanged <span class="token operator">+=</span> Connectivity_ConnectivityChanged<span class="token punctuation">;</span>
|
456
|
WeakReferenceMessenger<span class="token punctuation">.</span>Default<span class="token punctuation">.</span><span class="token function">Register</span><span class="token punctuation">(</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
457
|
<span class="token punctuation">}</span>
|
458
|
|
459
|
<span class="token comment">// ...</span>
|
460
|
<span class="token punctuation">}</span>
|
461
|
</pre><p>L'application tente ensuite de se reconnecter à la base de données du serveur, et si c'est le cas, un message <code>ConnectionSuccessMessage</code> est émis :</p>
|
462
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">ServerDAO</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IRecipient<span class="token punctuation"><</span>ConnectionChangedMessage<span class="token punctuation">></span></span></span>
|
463
|
<span class="token punctuation">{</span>
|
464
|
<span class="token comment">// ... </span>
|
465
|
|
466
|
<span class="token keyword keyword-public">public</span> <span class="token function">ServerDAO</span><span class="token punctuation">(</span><span class="token range operator">..</span><span class="token punctuation">.</span><span class="token punctuation">)</span>
|
467
|
<span class="token punctuation">{</span>
|
468
|
<span class="token comment">// ...</span>
|
469
|
WeakReferenceMessenger<span class="token punctuation">.</span>Default<span class="token punctuation">.</span><span class="token function">RegisterAll</span><span class="token punctuation">(</span><span class="token keyword keyword-this">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
470
|
<span class="token punctuation">}</span>
|
471
|
|
472
|
<span class="token comment">// ...</span>
|
473
|
|
474
|
<span class="token comment">// Fonction déclenchée à la reception du message ConnectionChangedMessage</span>
|
475
|
<span class="token keyword keyword-public">public</span> <span class="token return-type class-name"><span class="token keyword keyword-void">void</span></span> <span class="token function">Receive</span><span class="token punctuation">(</span><span class="token class-name">ConnectionChangedMessage</span> message<span class="token punctuation">)</span>
|
476
|
<span class="token punctuation">{</span>
|
477
|
|
478
|
<span class="token comment">// On se reconnecte</span>
|
479
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>message<span class="token punctuation">.</span>Value<span class="token punctuation">)</span>
|
480
|
<span class="token punctuation">{</span>
|
481
|
loadingIndicatorService<span class="token punctuation">.</span>LoadingMessage <span class="token operator">=</span> <span class="token string">"Tentative de connexion à "</span> <span class="token operator">+</span> settingsService<span class="token punctuation">.</span>ServerInfo<span class="token punctuation">.</span>Address<span class="token punctuation">;</span>
|
482
|
<span class="token function">OpenConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
483
|
<span class="token punctuation">}</span>
|
484
|
<span class="token comment">// On perd la connexion à la base</span>
|
485
|
<span class="token keyword keyword-else">else</span>
|
486
|
<span class="token punctuation">{</span>
|
487
|
loadingIndicatorService<span class="token punctuation">.</span>LoadingMessage <span class="token operator">=</span> <span class="token string">"Passage en mode hors ligne..."</span><span class="token punctuation">;</span>
|
488
|
<span class="token comment">// On ferme la connexion a la base pour éviter de timeout.</span>
|
489
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>connection <span class="token keyword keyword-is">is</span> <span class="token keyword keyword-not">not</span> <span class="token keyword keyword-null">null</span>
|
490
|
<span class="token operator">&&</span> connection<span class="token punctuation">.</span>State <span class="token operator">==</span> System<span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ConnectionState<span class="token punctuation">.</span>Open<span class="token punctuation">)</span>
|
491
|
connection<span class="token punctuation">.</span><span class="token function">CloseAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
492
|
<span class="token punctuation">}</span>
|
493
|
<span class="token punctuation">}</span>
|
494
|
|
495
|
<span class="token comment">// Fonction qui sert à l'ouverture de la connexion à la base MySQL du serveur</span>
|
496
|
<span class="token comment">// Envoi un message ConnectionSuccessMessage si la connexion à réussi ou non</span>
|
497
|
<span class="token keyword keyword-private">private</span> <span class="token return-type class-name"><span class="token keyword keyword-void">void</span></span> <span class="token function">OpenConnection</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
498
|
<span class="token punctuation">{</span>
|
499
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>connService<span class="token punctuation">.</span>IsReseauLocal<span class="token punctuation">)</span>
|
500
|
<span class="token punctuation">{</span>
|
501
|
<span class="token class-name">MySqlConnectionStringBuilder</span> builder <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
502
|
<span class="token punctuation">{</span>
|
503
|
<span class="token comment">// Les données de connexion sont stockées dans le SettingsService</span>
|
504
|
Server <span class="token operator">=</span> settingsService<span class="token punctuation">.</span>ServerInfo<span class="token punctuation">.</span>Address<span class="token punctuation">,</span>
|
505
|
Database <span class="token operator">=</span> <span class="token string">"sidexwifi_test"</span><span class="token punctuation">,</span>
|
506
|
UserID <span class="token operator">=</span> settingsService<span class="token punctuation">.</span>ServerInfo<span class="token punctuation">.</span>Username<span class="token punctuation">,</span>
|
507
|
Password <span class="token operator">=</span> settingsService<span class="token punctuation">.</span>ServerInfo<span class="token punctuation">.</span>Password
|
508
|
<span class="token punctuation">}</span><span class="token punctuation">;</span>
|
509
|
connection <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span>builder<span class="token punctuation">.</span>ConnectionString<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
510
|
|
511
|
<span class="token keyword keyword-try">try</span>
|
512
|
<span class="token punctuation">{</span>
|
513
|
connection<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
514
|
WeakReferenceMessenger<span class="token punctuation">.</span>Default<span class="token punctuation">.</span>
|
515
|
<span class="token function">Send</span><span class="token punctuation">(</span><span class="token keyword keyword-new">new</span> <span class="token constructor-invocation class-name">ConnectionSuccessMessage</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
516
|
<span class="token punctuation">}</span>
|
517
|
<span class="token keyword keyword-catch">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span><span class="token punctuation">)</span>
|
518
|
<span class="token punctuation">{</span>
|
519
|
WeakReferenceMessenger<span class="token punctuation">.</span>Default<span class="token punctuation">.</span>
|
520
|
<span class="token function">Send</span><span class="token punctuation">(</span><span class="token keyword keyword-new">new</span> <span class="token constructor-invocation class-name">ConnectionSuccessMessage</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
521
|
<span class="token punctuation">}</span>
|
522
|
<span class="token punctuation">}</span>
|
523
|
<span class="token punctuation">}</span>
|
524
|
<span class="token punctuation">}</span>
|
525
|
|
526
|
</pre><p>Reception du message <code>ConnectionSuccessMessage</code> par le Service <code>DataSenderService</code> :</p>
|
527
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">DataSenderService</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IRecipient<span class="token punctuation"><</span>ConnectionSuccessMessage<span class="token punctuation">></span></span></span>
|
528
|
<span class="token punctuation">{</span>
|
529
|
|
530
|
<span class="token comment">// ...</span>
|
531
|
<span class="token comment">// Fonction déclenchée à la reception du message ConnectionSuccessMessage</span>
|
532
|
<span class="token keyword keyword-public">public</span> <span class="token return-type class-name"><span class="token keyword keyword-void">void</span></span> <span class="token function">Receive</span><span class="token punctuation">(</span><span class="token class-name">ConnectionSuccessMessage</span> message<span class="token punctuation">)</span>
|
533
|
<span class="token punctuation">{</span>
|
534
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>message<span class="token punctuation">.</span>Value<span class="token punctuation">)</span>
|
535
|
<span class="token comment">// Fonction de synchronisation </span>
|
536
|
<span class="token comment">// NB: la fonction Await() vient de la librairie Prism.Core</span>
|
537
|
<span class="token comment">// --> permet d'appeler des fonctions asynchrones dans des procédures synchrones</span>
|
538
|
<span class="token function">SyncResultsAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
539
|
<span class="token punctuation">}</span>
|
540
|
<span class="token punctuation">}</span>
|
541
|
</pre><p>Dans l'application <code>SicpaExpe</code> la synchronisation est également faite à chaque envoi de donnée en ligne, car plusieurs utilisateurs peuvent être en cours d'utilisation et il est éssentiel que la synchronisation soit faite le plus souvent possible. De plus, le serveur étant hebergé sur le réseau local, les interruptions de connexions sont plus rare qu'une connexion passant par des <strong>WebServices</strong>, sur un serveur distant, par exemple.</p>
|
542
|
<p>Dans l'application <code>SIPMobile</code>, cette synchronisation n'est pas aussi cruciale et la connexion aux WebServices étant quelquefois aléatoire dans les UEs. Le système que j'ai adopté est un système de synchronisation manuelle, avec lequel l'utilisateur choisi d'envoyer ses données en attente d'envoi, et de re-télécharger les données de la base, dans un onglet spécial nommé "Synchronisation".</p>
|
543
|
<p>La gestion des modes en ligne et hors ligne est spécifique à l'utilisation des applications, ainsi que du type de BDD distante avec laquelle l'application intéragit. Plusieurs implémentations sont possibles et je recommande de se fixer des <em>règles</em> à l'avance pour rester cohérent dans le stockage des données, et éviter la désynchronisation.</p>
|
544
|
<h2 class="mume-header" id="appel-aux-ws-rest">Appel aux WS Rest</h2>
|
545
|
|
546
|
<p>L'application <code>SIPMobile</code> a été créée comme projet de migration de l'application SIPXami, développée en <em>Xamarin</em>, vers la technologie MAUI. Nous avons profité de cette migration pour remplacer l'utilisation des WS <code>SOAP + XML</code> par l'utilisation des WS <code>REST + JSON</code>.</p>
|
547
|
<p>Pour faire les différents appels aux WS, j'ai décidé d'utiliser la librairie <code>RestSharp</code>, soutenue par la fondation .NET :</p>
|
548
|
<ul>
|
549
|
<li><a href="https://restsharp.dev/intro.html#introduction">https://restsharp.dev/intro.html#introduction</a> (Documentation RestSharp)</li>
|
550
|
<li><a href="https://dotnetfoundation.org/">https://dotnetfoundation.org/</a> (Site de la fondation .NET)</li>
|
551
|
</ul>
|
552
|
<h3 class="mume-header" id="instanciation">Instanciation</h3>
|
553
|
|
554
|
<p>Pour utiliser la librairie, il faut tout d'abord instancier un objet <code>RestClient</code> de la façon suivante :</p>
|
555
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">ServerDAO</span>
|
556
|
<span class="token punctuation">{</span>
|
557
|
|
558
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">RestClient</span> client<span class="token punctuation">;</span>
|
559
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name"><span class="token keyword keyword-string">string</span></span> baseUrl <span class="token operator">=</span> <span class="token comment">//...</span>
|
560
|
<span class="token keyword keyword-private">private</span> <span class="token keyword keyword-readonly">readonly</span> <span class="token class-name">JsonSerializerOptions</span> serializerOptions<span class="token punctuation">;</span>
|
561
|
|
562
|
<span class="token keyword keyword-public">public</span> <span class="token function">ServerDAO</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
563
|
<span class="token punctuation">{</span>
|
564
|
<span class="token comment">// baseUrl est l'url de base du WS Rest</span>
|
565
|
client <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span><span class="token keyword keyword-new">new</span> <span class="token constructor-invocation class-name">RestClientOptions</span><span class="token punctuation">(</span>baseUrl<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
566
|
|
567
|
<span class="token comment">// options qui servent pour créer "parser" les objets</span>
|
568
|
<span class="token comment">// arrivants/sortants et sérialisés en JSON</span>
|
569
|
serializerOptions <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
570
|
<span class="token punctuation">{</span>
|
571
|
PropertyNamingPolicy <span class="token operator">=</span> JsonNamingPolicy<span class="token punctuation">.</span>CamelCase<span class="token punctuation">,</span>
|
572
|
WriteIndented <span class="token operator">=</span> <span class="token boolean">true</span>
|
573
|
<span class="token punctuation">}</span><span class="token punctuation">;</span>
|
574
|
<span class="token punctuation">}</span>
|
575
|
<span class="token punctuation">}</span>
|
576
|
</pre><p>Pour plus d'infos sur le <code>JSONSerializerOptions</code> :<br>
|
577
|
<a href="https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/configure-options?pivots=dotnet-7-0">https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/configure-options?pivots=dotnet-7-0</a></p>
|
578
|
<h3 class="mume-header" id="r%C3%A9cup%C3%A9rer-des-donn%C3%A9es">Récupérer des données</h3>
|
579
|
|
580
|
<p>Pour récupérer les données, il suffit de créer un objet <code>RestRequest</code> avec en options le <em>endpoint</em> de l'API REST que l'on souhaite attaquer. Cet objet est ensuite utilisé dans la fonction <code>GetAsync</code> proposé par notre objet <code>client</code> (Type: <code>RestClient</code>)</p>
|
581
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-async">async</span> <span class="token return-type class-name">Task<span class="token punctuation"><</span>List<span class="token punctuation"><</span>Categorie<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">GetCategories</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
582
|
<span class="token punctuation">{</span>
|
583
|
<span class="token comment">// Attention à l'URL qui est sensible aux majuscules !!</span>
|
584
|
<span class="token class-name">RestRequest</span> restRequest <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span><span class="token string">"categorieService/listeCategories"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
585
|
<span class="token keyword keyword-return">return</span> <span class="token keyword keyword-await">await</span> client<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>List<span class="token punctuation"><</span>Categorie<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>restRequest<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
586
|
<span class="token punctuation">}</span>
|
587
|
</pre><p>Sauf ajout d'annotations pour la désérialisation ou "<em>parsing</em>" des objets en Json, la conversion des objets se fait en fonction de leur nom de propriétés, et sans contraintes liée aux majuscules. Par exemple pour un objet <code>Categorie</code> venant de l'API suivant :</p>
|
588
|
<pre data-role="codeBlock" data-info="java" class="language-java"><span class="token class-name">Categorie</span>
|
589
|
<span class="token punctuation">{</span>
|
590
|
idCategorie <span class="token function">integer</span><span class="token punctuation">(</span>$int32<span class="token punctuation">)</span>
|
591
|
nomCategorie string
|
592
|
<span class="token punctuation">}</span>
|
593
|
</pre><p>L'objet correspondant dans l'application <code>SIPMobile</code> est :</p>
|
594
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">Categorie</span>
|
595
|
<span class="token punctuation">{</span>
|
596
|
<span class="token keyword keyword-public">public</span> <span class="token return-type class-name"><span class="token keyword keyword-int">int</span></span> IDCategorie <span class="token punctuation">{</span> <span class="token keyword keyword-get">get</span><span class="token punctuation">;</span> <span class="token keyword keyword-set">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
|
597
|
<span class="token keyword keyword-public">public</span> <span class="token return-type class-name"><span class="token keyword keyword-string">string</span></span> NomCategorie <span class="token punctuation">{</span> <span class="token keyword keyword-get">get</span><span class="token punctuation">;</span> <span class="token keyword keyword-set">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
|
598
|
<span class="token punctuation">}</span>
|
599
|
</pre><p>Et ainsi le <em>parsing</em> et donc l'affectation des valeurs se font automatiquement.</p>
|
600
|
<p>Exemple d'une fonction avec ajout d'un argument <code>uniteID</code>:</p>
|
601
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-async">async</span> <span class="token return-type class-name">Task<span class="token punctuation"><</span>List<span class="token punctuation"><</span>Zone<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">GetZonesFromUnite</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword keyword-int">int</span></span> uniteID<span class="token punctuation">)</span>
|
602
|
<span class="token punctuation">{</span>
|
603
|
<span class="token class-name">RestRequest</span> restRequest <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span><span class="token string">"zoneService/listeZonesByUnite?idUnite="</span> <span class="token operator">+</span> uniteID<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
604
|
<span class="token keyword keyword-return">return</span> <span class="token keyword keyword-await">await</span> client<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>List<span class="token punctuation"><</span>Zone<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>restRequest<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
605
|
<span class="token punctuation">}</span>
|
606
|
</pre><h3 class="mume-header" id="envoyer-des-donn%C3%A9es">Envoyer des données</h3>
|
607
|
|
608
|
<p>L'envoi des données est un petit plus complexe, car cela demande de créer une méthode POST et de sérialiser en JSON les objets à envoyer.</p>
|
609
|
<p>Dans l'application <code>SIPMobile</code>, l'envoi de données se fait dans l'url de la requête, ce qui donne le code suivant pour l'envoi de plusieurs objets <code>Mouvement</code> (une liste), par exemple.</p>
|
610
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-async">async</span> <span class="token return-type class-name">Task<span class="token punctuation"><</span><span class="token keyword keyword-bool">bool</span><span class="token punctuation">></span></span> <span class="token function">SaveMouvementList</span><span class="token punctuation">(</span><span class="token class-name">List<span class="token punctuation"><</span>Mouvement<span class="token punctuation">></span></span> mouvementList<span class="token punctuation">)</span>
|
611
|
<span class="token punctuation">{</span>
|
612
|
<span class="token class-name">RestRequest</span> restRequest <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span><span class="token string">"mouvementService/insertMouvementsTotalByIndividu"</span><span class="token punctuation">,</span> Method<span class="token punctuation">.</span>Post<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
613
|
<span class="token comment">// Serialisation au format JSON du mouvement</span>
|
614
|
<span class="token class-name"><span class="token keyword keyword-string">string</span></span> mouvementsJson <span class="token operator">=</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span>mouvementList<span class="token punctuation">,</span> serializerOptions<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
615
|
|
616
|
<span class="token comment">// Ajout de l'objet sérialisé en argument </span>
|
617
|
<span class="token comment">// NB: les noms d'arguments soivent respecter les majuscules!</span>
|
618
|
restRequest<span class="token punctuation">.</span>AddParameter
|
619
|
<span class="token punctuation">(</span><span class="token string">"mouvementsJson"</span><span class="token punctuation">,</span> <span class="token comment">// le nom du paramètre (attention à la case!)</span>
|
620
|
mouvementsJson<span class="token punctuation">,</span> <span class="token comment">// l'objet sérialisé en json</span>
|
621
|
ParameterType<span class="token punctuation">.</span>QueryString <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// le paramètre est dans l'url (=query)</span>
|
622
|
|
623
|
<span class="token keyword keyword-try">try</span>
|
624
|
<span class="token punctuation">{</span>
|
625
|
<span class="token comment">// Envoi de la requête</span>
|
626
|
<span class="token keyword keyword-await">await</span> client<span class="token punctuation">.</span><span class="token function">PostAsync</span><span class="token punctuation">(</span>restRequest<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
627
|
<span class="token keyword keyword-return">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
|
628
|
<span class="token punctuation">}</span>
|
629
|
<span class="token keyword keyword-catch">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span><span class="token punctuation">)</span>
|
630
|
<span class="token punctuation">{</span>
|
631
|
<span class="token keyword keyword-return">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
|
632
|
<span class="token punctuation">}</span>
|
633
|
<span class="token punctuation">}</span>
|
634
|
</pre><p>À noter l'utilisation de l'argument <code>ParameterType.QueryString</code> dans la fonction <code>AddParameter</code> pour répondre à la nécessité d'avoir les arguments dans l'url de la requête. Sans ajout de ce paramètre, les objets à envoyer sont ajoutés au champ <code><body></code> de la requête.</p>
|
635
|
<p>NB: sur SIPMobile, j'ai recontré un problème ou le format <code>DateTime</code> était donné avec le fuseaux horaires (heure d'été = +2h) et la sérialisation en JSON comprenait ce paramètre. Lors de l'enregistrement sur le serveur, la date était convertie avec 2h de moins...<br>
|
636
|
Pour contourner le problème je créé les objets <code>Datetime</code> de la façon suivante :</p>
|
637
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token comment">// Problème avec fuseaux horaire :</span>
|
638
|
<span class="token class-name">DateTime</span> dateChangement <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">;</span>
|
639
|
<span class="token comment">// Sans le fuseaux horaire :</span>
|
640
|
<span class="token class-name">DateTime</span> dateChangement <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span>DateTime<span class="token punctuation">.</span>Now<span class="token punctuation">.</span>Ticks<span class="token punctuation">,</span> DateTimeKind<span class="token punctuation">.</span>Utc<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
641
|
</pre><h2 class="mume-header" id="permissions-sp%C3%A9cifiques-fichiers-externes-android">Permissions spécifiques fichiers externes Android</h2>
|
642
|
|
643
|
<p>Pour certaines applications, il peut être indispensable d'accéder aux fichiers "externes" de l'appareil, c'est-à-dire les fichiers qui ne sont pas propre à l'application et donc communs à toutes les applications. Parmi ces fichiers, on peut par exemple trouver les fichiers issus des téléchargements.<br>
|
644
|
Avant Android 11, leur accès demandaient la simple addition au Manifest et acceptation des permissions <code>StorageRead</code> et <code>StorageWrite</code>, mais depuis, c'est devenu un peu plus compliqué...</p>
|
645
|
<p>Plus d'info sur <a href="https://developer.android.com/training/data-storage?hl=fr#permissions">https://developer.android.com/training/data-storage?hl=fr#permissions</a> (Documentation Android officielle).</p>
|
646
|
<p>Méthode sous Android inférieur à 11 :<br>
|
647
|
Déclarer les permissions dans le Manifest Android : Nom_du_projet > Platforms > Android > AndroidManifest.xml > Clique-droit > Ouvrir Avec > Editeur XML.</p>
|
648
|
<pre data-role="codeBlock" data-info="xml" class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>manifest</span> <span class="token attr-name">...</span><span class="token punctuation">></span></span>
|
649
|
<span class="token comment"><!-- ... --></span>
|
650
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>uses-permission</span> <span class="token attr-name"><span class="token namespace">android:</span>name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>android.permission.READ_EXTERNAL_STORAGE<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
|
651
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>uses-permission</span> <span class="token attr-name"><span class="token namespace">android:</span>name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>android.permission.WRITE_EXTERNAL_STORAGE<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
|
652
|
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>manifest</span><span class="token punctuation">></span></span>
|
653
|
</pre><p>Puis au lancement de l'application, vérifier si les permissions sont allouées :</p>
|
654
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">RequestAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>Permissions<span class="token punctuation">.</span>StorageRead<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
655
|
<span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">RequestAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>Permissions<span class="token punctuation">.</span>StorageWrite<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
656
|
</pre><p>Sur les version d'android supérieures ou égales à 11, une seule permissions doit être ajoutée au Manifest:</p>
|
657
|
<pre data-role="codeBlock" data-info="xml" class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>manifest</span> <span class="token attr-name">...</span><span class="token punctuation">></span></span>
|
658
|
<span class="token comment"><!-- ... --></span>
|
659
|
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>uses-permission</span> <span class="token attr-name"><span class="token namespace">android:</span>name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>android.permission.MANAGE_EXTERNAL_STORAGE<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
|
660
|
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>manifest</span><span class="token punctuation">></span></span>
|
661
|
</pre><p>Mais en outre, l'utilisateur doit cocher une demande de permissions dans les paramètres du système qui autorise l'application à accéder à tous les fichiers de l'appareil.<br>
|
662
|
Pour cela, créer une classe que l'on peut nommer <code>ManageStoragePerm</code>, par exemple, et qui hérite de <code>Permissions.BasePlatformPermission</code>:</p>
|
663
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-public">public</span> <span class="token keyword keyword-class">class</span> <span class="token class-name">ManageStoragePerm</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Permissions<span class="token punctuation">.</span>BasePlatformPermission</span></span>
|
664
|
<span class="token punctuation">{</span>
|
665
|
<span class="token preprocessor property">#<span class="token directive keyword">if</span> ANDROID</span>
|
666
|
<span class="token comment">// Fonction qui permet de vérifier si la permission a été allouée</span>
|
667
|
<span class="token keyword keyword-public">public</span> <span class="token keyword keyword-override">override</span> <span class="token return-type class-name">Task<span class="token punctuation"><</span>PermissionStatus<span class="token punctuation">></span></span> <span class="token function">CheckStatusAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
668
|
<span class="token punctuation">{</span>
|
669
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>Android<span class="token punctuation">.</span>OS<span class="token punctuation">.</span>Environment<span class="token punctuation">.</span>IsExternalStorageManager<span class="token punctuation">)</span>
|
670
|
<span class="token punctuation">{</span>
|
671
|
<span class="token keyword keyword-return">return</span> Task<span class="token punctuation">.</span><span class="token function">FromResult</span><span class="token punctuation">(</span>PermissionStatus<span class="token punctuation">.</span>Granted<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
672
|
<span class="token punctuation">}</span> <span class="token keyword keyword-else">else</span>
|
673
|
<span class="token punctuation">{</span>
|
674
|
<span class="token keyword keyword-return">return</span> Task<span class="token punctuation">.</span><span class="token function">FromResult</span><span class="token punctuation">(</span>PermissionStatus<span class="token punctuation">.</span>Unknown<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
675
|
<span class="token punctuation">}</span>
|
676
|
<span class="token punctuation">}</span>
|
677
|
|
678
|
<span class="token comment">// Fonction qui permet d'ouvrir la fenêtre dans les paramètres de l'appareil</span>
|
679
|
<span class="token keyword keyword-public">public</span> <span class="token keyword keyword-override">override</span> <span class="token return-type class-name">Task<span class="token punctuation"><</span>PermissionStatus<span class="token punctuation">></span></span> <span class="token function">RequestAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
|
680
|
<span class="token punctuation">{</span>
|
681
|
<span class="token keyword keyword-try">try</span>
|
682
|
<span class="token punctuation">{</span>
|
683
|
<span class="token class-name">Android<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Uri</span> uri <span class="token operator">=</span> Android<span class="token punctuation">.</span>Net<span class="token punctuation">.</span>Uri<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token string">"package:"</span> <span class="token operator">+</span> AppInfo<span class="token punctuation">.</span>PackageName<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
684
|
<span class="token class-name">Android<span class="token punctuation">.</span>Content<span class="token punctuation">.</span>Intent</span> intent <span class="token operator">=</span>
|
685
|
<span class="token keyword keyword-new">new</span><span class="token punctuation">(</span>Android<span class="token punctuation">.</span>Provider<span class="token punctuation">.</span>Settings<span class="token punctuation">.</span>ActionManageAppAllFilesAccessPermission<span class="token punctuation">,</span> uri<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
686
|
Platform<span class="token punctuation">.</span>CurrentActivity<span class="token punctuation">.</span><span class="token function">StartActivity</span><span class="token punctuation">(</span>intent<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
687
|
<span class="token punctuation">}</span>
|
688
|
<span class="token keyword keyword-catch">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span><span class="token punctuation">)</span>
|
689
|
<span class="token punctuation">{</span>
|
690
|
<span class="token class-name">Android<span class="token punctuation">.</span>Content<span class="token punctuation">.</span>Intent</span> intent <span class="token operator">=</span> <span class="token keyword keyword-new">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
691
|
intent<span class="token punctuation">.</span><span class="token function">SetAction</span><span class="token punctuation">(</span>Android<span class="token punctuation">.</span>Provider<span class="token punctuation">.</span>Settings<span class="token punctuation">.</span>ActionManageAllFilesAccessPermission<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
692
|
Platform<span class="token punctuation">.</span>CurrentActivity<span class="token punctuation">.</span><span class="token function">StartActivity</span><span class="token punctuation">(</span>intent<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
693
|
<span class="token punctuation">}</span>
|
694
|
<span class="token keyword keyword-return">return</span> Task<span class="token punctuation">.</span><span class="token function">FromResult</span><span class="token punctuation">(</span>PermissionStatus<span class="token punctuation">.</span>Unknown<span class="token punctuation">)</span><span class="token punctuation">;</span>
|
695
|
<span class="token punctuation">}</span>
|
696
|
<span class="token preprocessor property">#<span class="token directive keyword">endif</span></span>
|
697
|
|
698
|
<span class="token punctuation">}</span>
|
699
|
</pre><p>Exemple de code de vérification des permissions pour la gestion du stockage externe sur l'application <code>SIPMobile</code>, utilisée sur des appareils cibles dont la version Android peut aller de 7.1 à 13 (à l'heure de la rédaction de ce document) :</p>
|
700
|
<pre data-role="codeBlock" data-info="csharp" class="language-csharp"><span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>DeviceInfo<span class="token punctuation">.</span>Version<span class="token punctuation">.</span>Major <span class="token operator">>=</span> <span class="token number">11</span><span class="token punctuation">)</span>
|
701
|
<span class="token punctuation">{</span>
|
702
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">CheckStatusAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>ManageStoragePerm<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> PermissionStatus<span class="token punctuation">.</span>Unknown<span class="token punctuation">)</span>
|
703
|
<span class="token punctuation">{</span>
|
704
|
<span class="token keyword keyword-await">await</span> Application<span class="token punctuation">.</span>Current<span class="token punctuation">.</span>MainPage
|
705
|
<span class="token punctuation">.</span><span class="token function">DisplayAlert</span><span class="token punctuation">(</span>
|
706
|
<span class="token string">"Information"</span><span class="token punctuation">,</span>
|
707
|
<span class="token string">"Veuillez cocher l'option d'accès aux fichier dans la fenêtre suivante."</span><span class="token punctuation">,</span>
|
708
|
<span class="token string">"Suivant"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
709
|
<span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">RequestAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>ManageStoragePerm<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
710
|
<span class="token punctuation">}</span>
|
711
|
|
712
|
<span class="token comment">// On ne "catch" pas le retour de l'activité donc on attend avec une boucle de 15 secondes</span>
|
713
|
<span class="token class-name"><span class="token keyword keyword-int">int</span></span> count <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
|
714
|
<span class="token keyword keyword-while">while</span> <span class="token punctuation">(</span><span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">CheckStatusAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>ManageStoragePerm<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> PermissionStatus<span class="token punctuation">.</span>Unknown <span class="token operator">&&</span> count <span class="token operator"><</span> <span class="token number">15</span><span class="token punctuation">)</span>
|
715
|
<span class="token punctuation">{</span>
|
716
|
loadingIndicatorService<span class="token punctuation">.</span>Message <span class="token operator">=</span> <span class="token string">"Attente des autorisations..."</span><span class="token punctuation">;</span>
|
717
|
<span class="token keyword keyword-await">await</span> Task<span class="token punctuation">.</span><span class="token function">Delay</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
718
|
count<span class="token operator">++</span><span class="token punctuation">;</span>
|
719
|
<span class="token punctuation">}</span>
|
720
|
|
721
|
<span class="token comment">// Si à la fin des 15 secondes, on a toujours pas l'autorisation</span>
|
722
|
<span class="token comment">// Demande à l'utilisateur de relancer l'appli</span>
|
723
|
<span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span><span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">CheckStatusAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>ManageStoragePerm<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> PermissionStatus<span class="token punctuation">.</span>Granted<span class="token punctuation">)</span>
|
724
|
<span class="token punctuation">{</span>
|
725
|
<span class="token keyword keyword-await">await</span> Application<span class="token punctuation">.</span>Current<span class="token punctuation">.</span>MainPage
|
726
|
<span class="token punctuation">.</span><span class="token function">DisplayAlert</span><span class="token punctuation">(</span>
|
727
|
<span class="token string">"Erreur"</span><span class="token punctuation">,</span>
|
728
|
<span class="token string">"L'autorisation d'accès aux fichiers est requise pour le bon fonctionnement de l'application. Relancez l'application."</span><span class="token punctuation">,</span>
|
729
|
<span class="token string">"OK"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
730
|
loadingIndicatorService<span class="token punctuation">.</span>IsBusy <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
|
731
|
<span class="token keyword keyword-return">return</span><span class="token punctuation">;</span>
|
732
|
<span class="token punctuation">}</span>
|
733
|
<span class="token punctuation">}</span>
|
734
|
<span class="token comment">// Version en dessous de 11</span>
|
735
|
<span class="token keyword keyword-else">else</span>
|
736
|
<span class="token punctuation">{</span>
|
737
|
<span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">RequestAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>Permissions<span class="token punctuation">.</span>StorageRead<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
738
|
<span class="token keyword keyword-await">await</span> Permissions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">RequestAsync</span><span class="token generic class-name"><span class="token punctuation"><</span>Permissions<span class="token punctuation">.</span>StorageWrite<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
|
739
|
<span class="token punctuation">}</span>
|
740
|
</pre><p>On y voit l'appel à 3 reprises des fonctions de notre classe <code>ManageStoragePerms</code>. La fenêtre des paramètres ou il est demandé de cocher une option ressemble à la capture d'écran suivante sur un appareil avec Android 12.</p>
|
741
|
<p><img src="capture_permissions.png" alt="capture permission"></p>
|
742
|
|
743
|
</div>
|
744
|
|
745
|
|
746
|
|
747
|
|
748
|
|
749
|
|
750
|
|
751
|
|
752
|
|
753
|
|
754
|
|
755
|
</body></html>
|