在 Outlook 加载项的 WPF 表单中使用的 Newtonsoft Json 找不到应用程序配置
Newtonsoft Json used inside WPF Form of an Outlook Add-in does not find Application Configuration
我使用 Visual Studio 2013 为 Outlook 2010 - 2016 创建了一个 Outlook 加载项。
Outlook 加载项包含 2 个 WPF 表单,它们使用 REST api 并执行 http rest 调用。结果是 Json 并且会被 Newtonsoft Json.
反序列化
在调试模式下一切正常。
一旦我使用 wix 创建安装工具并安装加载项,我就遇到了 Json 对象不再反序列化的问题。原因是加载项找不到配置文件。
我发现 Outlook 在 Office Outlook 程序路径中寻找 outlook.exe.config。但这不是我想要的,因为我不想对该中央配置文件进行任何可能影响其他应用程序的更改。
弄清楚这一点后,我发现了这个简洁的小代码片段,可以在我的加载项启动时更改配置文件:
using System;
using System.Configuration;
using System.Linq;
using System.Reflection;
public abstract class AppConfig : IDisposable
{
public static AppConfig Change(string path)
{
return new ChangeAppConfig(path);
}
public abstract void Dispose();
private class ChangeAppConfig : AppConfig
{
private readonly string oldConfig =
AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE").ToString();
private bool disposedValue;
public ChangeAppConfig(string path)
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", path);
ResetConfigMechanism();
}
public override void Dispose()
{
if (!disposedValue)
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", oldConfig);
ResetConfigMechanism();
disposedValue = true;
}
GC.SuppressFinalize(this);
}
private static void ResetConfigMechanism()
{
typeof(ConfigurationManager)
.GetField("s_initState", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, 0);
typeof(ConfigurationManager)
.GetField("s_configSystem", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, null);
typeof(ConfigurationManager)
.Assembly.GetTypes()
.Where(x => x.FullName ==
"System.Configuration.ClientConfigPaths")
.First()
.GetField("s_current", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, null);
}
}
}
将应用程序配置更改为正确的路径后,我可以通过 System.Configuration.ConfgurationManager 从配置文件中读取所有设置(我在安装发行版后在运行时尝试过并将其写入文件流)。
但是仍然......在尝试将 Json 反序列化为对象时,我收到以下错误:
System.IO.FileLoadException: Could not load file or assembly
'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral,
PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The
located assembly's manifest definition does not match the assembly
reference. (Exception from HRESULT: 0x80131040) File name:
'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral,
PublicKeyToken=30ad4fe6b2a6aeed' at
System.Net.Http.Formatting.JsonMediaTypeFormatter..ctor() at
System.Net.Http.Formatting.MediaTypeFormatterCollection.CreateDefaultFormatters()
at
XXXXXXXXXXXXXXXX.<>c__DisplayClass16.b__14(Task`1
task)
=== Pre-bind state information === LOG: DisplayName = Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed
(Fully-specified) LOG: Appbase = file:///C:/Program Files/XXXXXXXXX/
LOG: Initial PrivatePath = NULL Calling assembly :
System.Net.Http.Formatting, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=31bf3856ad364e35.
=== LOG: This bind starts in default load context. LOG: No application configuration file found. LOG: Using host configuration file: LOG:
Using machine configuration file from
C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
LOG: Post-policy reference: Newtonsoft.Json, Version=4.5.0.0,
Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed LOG: Attempting
download of new URL file:///C:/Program
Files/XXXXXX/Newtonsoft.Json.DLL. WRN: Comparing the assembly name
resulted in the mismatch: Major Version ERR: Failed to complete setup
of assembly (hr = 0x80131040). Probing terminated.
这是我的 app.config 文件的样子:
<?xml version="1.0" encoding="utf-8"?>>
<configuration>
<appSettings>
<add key="ApiUrl" value="http://localhost:56610/api" />
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
有谁知道为什么Newtonsoft Json的配置不起作用,为什么我提供的配置没有被使用?
非常感谢您的帮助。
我找到了解决问题的方法。
我的初始代码如下所示:
public DomainUser FindUserBySecurityIdentifier(SecurityIdentifier securityIdentifier)
{
var domainUser = new DomainUser(securityIdentifier.ToString());
string domainUserJson = Newtonsoft.Json.JsonConvert.SerializeObject(domainUser);
HttpContent content = new StringContent(domainUserJson);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
Exception exception = null;
var item = DomainUser.EMPTY;
var endpointUrl = ConstructEndpointUrl("api/FindUserBySecurityIdentifier");
if (!string.IsNullOrWhiteSpace(endpointUrl))
{
Task postLink = HttpClient.PostAsync(endpointUrl, content).ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
var response = task.Result;
if (response.IsSuccessStatusCode)
{
// The error occured at this line
item = response.Content.ReadAsAsync<DomainUser>().Result;
}
else
{
exception = new Exception(response.ToString());
}
}
else
{
exception = new Exception("The http request to the server did not run to completion. Please contact the administrator");
}
});
postLink.Wait();
}
if (exception != null)
{
throw exception;
}
return item;
}
在我像下面这样更改我的代码后,它终于可以工作了
public DomainUser FindUserBySecurityIdentifier(SecurityIdentifier securityIdentifier)
{
var domainUser = new DomainUser(securityIdentifier.ToString());
string domainUserJson = Newtonsoft.Json.JsonConvert.SerializeObject(domainUser);
HttpContent content = new StringContent(domainUserJson);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
Exception exception = null;
var item = DomainUser.EMPTY;
var endpointUrl = ConstructEndpointUrl("api/FindUserBySecurityIdentifier");
if (!string.IsNullOrWhiteSpace(endpointUrl))
{
Task postLink = HttpClient.PostAsync(endpointUrl, content).ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
var response = task.Result;
if (response.IsSuccessStatusCode)
{
// after I read the result into a string first and deserialized it with JsonConvert.DeserializeObject it worked
var objectAsString = response.Content.ReadAsStringAsync().Result;
item = JsonConvert.DeserializeObject<DomainUser>(objectAsString);
}
else
{
exception = new Exception(response.ToString());
}
}
else
{
exception = new Exception("The http request to the server did not run to completion. Please contact the administrator");
}
});
postLink.Wait();
}
if (exception != null)
{
throw exception;
}
return item;
}
我使用 Visual Studio 2013 为 Outlook 2010 - 2016 创建了一个 Outlook 加载项。 Outlook 加载项包含 2 个 WPF 表单,它们使用 REST api 并执行 http rest 调用。结果是 Json 并且会被 Newtonsoft Json.
反序列化在调试模式下一切正常。 一旦我使用 wix 创建安装工具并安装加载项,我就遇到了 Json 对象不再反序列化的问题。原因是加载项找不到配置文件。 我发现 Outlook 在 Office Outlook 程序路径中寻找 outlook.exe.config。但这不是我想要的,因为我不想对该中央配置文件进行任何可能影响其他应用程序的更改。
弄清楚这一点后,我发现了这个简洁的小代码片段,可以在我的加载项启动时更改配置文件:
using System;
using System.Configuration;
using System.Linq;
using System.Reflection;
public abstract class AppConfig : IDisposable
{
public static AppConfig Change(string path)
{
return new ChangeAppConfig(path);
}
public abstract void Dispose();
private class ChangeAppConfig : AppConfig
{
private readonly string oldConfig =
AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE").ToString();
private bool disposedValue;
public ChangeAppConfig(string path)
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", path);
ResetConfigMechanism();
}
public override void Dispose()
{
if (!disposedValue)
{
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", oldConfig);
ResetConfigMechanism();
disposedValue = true;
}
GC.SuppressFinalize(this);
}
private static void ResetConfigMechanism()
{
typeof(ConfigurationManager)
.GetField("s_initState", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, 0);
typeof(ConfigurationManager)
.GetField("s_configSystem", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, null);
typeof(ConfigurationManager)
.Assembly.GetTypes()
.Where(x => x.FullName ==
"System.Configuration.ClientConfigPaths")
.First()
.GetField("s_current", BindingFlags.NonPublic |
BindingFlags.Static)
.SetValue(null, null);
}
}
}
将应用程序配置更改为正确的路径后,我可以通过 System.Configuration.ConfgurationManager 从配置文件中读取所有设置(我在安装发行版后在运行时尝试过并将其写入文件流)。
但是仍然......在尝试将 Json 反序列化为对象时,我收到以下错误:
System.IO.FileLoadException: Could not load file or assembly 'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) File name: 'Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' at System.Net.Http.Formatting.JsonMediaTypeFormatter..ctor() at System.Net.Http.Formatting.MediaTypeFormatterCollection.CreateDefaultFormatters() at XXXXXXXXXXXXXXXX.<>c__DisplayClass16.b__14(Task`1 task)
=== Pre-bind state information === LOG: DisplayName = Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed (Fully-specified) LOG: Appbase = file:///C:/Program Files/XXXXXXXXX/ LOG: Initial PrivatePath = NULL Calling assembly : System.Net.Http.Formatting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35. === LOG: This bind starts in default load context. LOG: No application configuration file found. LOG: Using host configuration file: LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config. LOG: Post-policy reference: Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed LOG: Attempting download of new URL file:///C:/Program Files/XXXXXX/Newtonsoft.Json.DLL. WRN: Comparing the assembly name resulted in the mismatch: Major Version ERR: Failed to complete setup of assembly (hr = 0x80131040). Probing terminated.
这是我的 app.config 文件的样子:
<?xml version="1.0" encoding="utf-8"?>>
<configuration>
<appSettings>
<add key="ApiUrl" value="http://localhost:56610/api" />
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
有谁知道为什么Newtonsoft Json的配置不起作用,为什么我提供的配置没有被使用?
非常感谢您的帮助。
我找到了解决问题的方法。 我的初始代码如下所示:
public DomainUser FindUserBySecurityIdentifier(SecurityIdentifier securityIdentifier)
{
var domainUser = new DomainUser(securityIdentifier.ToString());
string domainUserJson = Newtonsoft.Json.JsonConvert.SerializeObject(domainUser);
HttpContent content = new StringContent(domainUserJson);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
Exception exception = null;
var item = DomainUser.EMPTY;
var endpointUrl = ConstructEndpointUrl("api/FindUserBySecurityIdentifier");
if (!string.IsNullOrWhiteSpace(endpointUrl))
{
Task postLink = HttpClient.PostAsync(endpointUrl, content).ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
var response = task.Result;
if (response.IsSuccessStatusCode)
{
// The error occured at this line
item = response.Content.ReadAsAsync<DomainUser>().Result;
}
else
{
exception = new Exception(response.ToString());
}
}
else
{
exception = new Exception("The http request to the server did not run to completion. Please contact the administrator");
}
});
postLink.Wait();
}
if (exception != null)
{
throw exception;
}
return item;
}
在我像下面这样更改我的代码后,它终于可以工作了
public DomainUser FindUserBySecurityIdentifier(SecurityIdentifier securityIdentifier)
{
var domainUser = new DomainUser(securityIdentifier.ToString());
string domainUserJson = Newtonsoft.Json.JsonConvert.SerializeObject(domainUser);
HttpContent content = new StringContent(domainUserJson);
content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
Exception exception = null;
var item = DomainUser.EMPTY;
var endpointUrl = ConstructEndpointUrl("api/FindUserBySecurityIdentifier");
if (!string.IsNullOrWhiteSpace(endpointUrl))
{
Task postLink = HttpClient.PostAsync(endpointUrl, content).ContinueWith(task =>
{
if (task.Status == TaskStatus.RanToCompletion)
{
var response = task.Result;
if (response.IsSuccessStatusCode)
{
// after I read the result into a string first and deserialized it with JsonConvert.DeserializeObject it worked
var objectAsString = response.Content.ReadAsStringAsync().Result;
item = JsonConvert.DeserializeObject<DomainUser>(objectAsString);
}
else
{
exception = new Exception(response.ToString());
}
}
else
{
exception = new Exception("The http request to the server did not run to completion. Please contact the administrator");
}
});
postLink.Wait();
}
if (exception != null)
{
throw exception;
}
return item;
}