System.Net.WebException : POST Failed while UiTesting on local emulator - c#

I am trying to verify that the UI elements of my LoginPage work as expected. The test is supposed to write a user name, user password and server adress into the corresponding entry fields then tap the sign in button.
The UITest loads the LoginPage and then fails with this exception:
Nachricht:
System.Net.WebException : POST Failed
Stapelüberwachung:
HttpClient.HandleHttpError(String method, Exception exception, ExceptionPolicy exceptionPolicy)
HttpClient.SendData(String endpoint, String method, HttpContent content, ExceptionPolicy exceptionPolicy, Nullable`1 timeOut)
HttpClient.Post(String endpoint, String arguments, ExceptionPolicy exceptionPolicy, Nullable`1 timeOut)
HttpApplicationStarter.Execute(String intentJson)
AndroidAppLifeCycle.LaunchApp(String appPackageName, ApkFile testServerApkFile, Int32 testServerPort)
AndroidApp.ctor(IAndroidAppConfiguration appConfiguration, IExecutor executor)
AndroidAppConfigurator.StartApp(AppDataMode appDataMode)
ReplDroid.ConnectToDeviceAndr() Zeile 33
ReplDroid.BeforeEachTest() Zeile 24
Now the really interesting part is that it worked before, but after I added code which threw and exception (and removed it again) it is stuck in this POST Failed exception.
To fix my problem I already tried: doing a factory reset of the emulator, cleaning the solution and rebuilding it, deleting the .vs folder, restarting vs and the emulator, testing on different emulator instances and restarting the entire PC. Nothing worked reliably.
The code of my test project is below:
using NUnit.Framework;
using System;
using System.Threading;
using Xamarin.UITest;
namespace REPL
{
[TestFixture(Platform.Android)]
public class ReplDroid
{
IApp app;
Platform platform;
LoginPageController loginPageController;
MainMenuController mainMenuController;
public ReplDroid(Platform platform)
{
this.platform = platform;
}
[SetUp]
public void BeforeEachTest()
{
ConnectToDeviceAndr();
loginPageController = new LoginPageController(app);
mainMenuController = new MainMenuController(app);
}
private void ConnectToDeviceAndr()
{
app = ConfigureApp.Android
.ApkFile(ApkLocation)
.PreferIdeSettings()
.EnableLocalScreenshots()
.StartApp();
}
[Test]
public void Login()
{
loginPageController.EnterUserNumber(UserNumber);
loginPageController.EnterPassword(Password);
loginPageController.EnterServerUrl(ServerUrl);
loginPageController.SignIn();
}
}
}
using Xamarin.UITest;
using Xamarin.UITest.Queries;
namespace REPL
{
public class LoginPageController
{
private readonly IApp app;
private const string UserEntryAutomationId = "UserEntry";
private const string PasswordEntryAutomationId = "PasswordEntry";
private const string ServerUrlEntryAutomationId = "ServerUrlEntry";
private const string LoginButtonAutomationId = "LoginButton";
public LoginPageController(IApp app)
{
this.app = app;
}
public AppResult GetUserEntry() => app.GetByAutomationId(UserEntryAutomationId);
public AppResult GetPasswordEntry() => app.GetByAutomationId(PasswordEntryAutomationId);
public AppResult GetServerUrlEntry() => app.GetByAutomationId(ServerUrlEntryAutomationId);
public AppResult GetLoginButton() => app.GetByAutomationId(LoginButtonAutomationId);
public void EnterUserNumber(string userNumber) => app.WaitAndContinue(UserEntryAutomationId).EnterText(userNumber);
public void EnterPassword(string password) => app.WaitAndContinue(PasswordEntryAutomationId).EnterText(password);
public void EnterServerUrl(string serverUrl) => app.WaitAndContinue(ServerUrlEntryAutomationId).EnterText(serverUrl);
public void SignIn()
{
app.DismissKeyboard();
app.WaitAndContinue(LoginButtonAutomationId).Tap();
}
}
}
All similar posts I could find were about failing to test on App Center, if I somehow missed one relevant to my problem please point me in the right direction.

I had the same problem.
running an empty android Xamarin forms UiTest in my IDE (Jetbrains Rider)
[Test]
public void WelcomeTextIsDisplayed()
{
AppResult[] results = app.WaitForElement(c => c.Marked("Welcome to Xamarin.Forms!"));
app.Screenshot("Welcome screen.");
Assert.IsTrue(results.Any());
}
I fixed it by updating the Nudget package Xamarin.UiTests from 2.2.4 to the newest (3.2.0 currently)

Related

"Trust anchor for certification path not found." in a .NET Maui Project trying to contact a local .NET WebApi

I'm new to mobile development and I'm trying to have my .NET Maui app connect to a local ASP.NET Core website (API).
I am currently blocked by this exception:
System.Net.WebException: 'java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.'
I have followed this article https://learn.microsoft.com/en-us/xamarin/cross-platform/deploy-test/connect-to-local-web-services#bypass-the-certificate-security-check
Running dotnet dev-certs https --trust returns A valid HTTPS certificate is already present.
My current code is:
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert.Issuer.Equals("CN=localhost"))
return true;
return errors == System.Net.Security.SslPolicyErrors.None;
};
var httpclient = new HttpClient(handler);
var test = await httpclient.PostAsync($"https://10.0.2.2:44393/" + uri, new StringContent(serializedItem, Encoding.UTF8, "application/json"));
But the thing is that i never enter the ServerCertificateCustomValidationCallback.
I also tried
ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
{
return true;
};
But no luck with that either.
Did something change in .NET MAUI?
I encountered exactly the same problem when I was trying to get SignalR client to connect my local test server. After digging into the source code, I found that HttpClientHandler actually uses AndroidMessageHandler as its underlying handler.
While AndroidMessageHandler implements a ServerCertificateCustomValidationCallback property, its value is never used when sending requests. This issue is addressed in this pull request.
For now, to disable server certificate verification on Android, you can implement a custom TrustProvider which will bypass any certificate verification:
using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;
namespace MyApp.Platforms.Android
{
internal class DangerousTrustProvider : Provider
{
private const string TRUST_PROVIDER_ALG = "DangerousTrustAlgorithm";
private const string TRUST_PROVIDER_ID = "DangerousTrustProvider";
public DangerousTrustProvider() : base(TRUST_PROVIDER_ID, 1, string.Empty)
{
var key = "TrustManagerFactory." + DangerousTrustManagerFactory.GetAlgorithm();
var val = Java.Lang.Class.FromType(typeof(DangerousTrustManagerFactory)).Name;
Put(key, val);
}
public static void Register()
{
Provider registered = Security.GetProvider(TRUST_PROVIDER_ID);
if (null == registered)
{
Security.InsertProviderAt(new DangerousTrustProvider(), 1);
Security.SetProperty("ssl.TrustManagerFactory.algorithm", TRUST_PROVIDER_ALG);
}
}
public class DangerousTrustManager : X509ExtendedTrustManager
{
public override void CheckClientTrusted(X509Certificate[] chain, string authType, Socket socket) { }
public override void CheckClientTrusted(X509Certificate[] chain, string authType, SSLEngine engine) { }
public override void CheckClientTrusted(X509Certificate[] chain, string authType) { }
public override void CheckServerTrusted(X509Certificate[] chain, string authType, Socket socket) { }
public override void CheckServerTrusted(X509Certificate[] chain, string authType, SSLEngine engine) { }
public override void CheckServerTrusted(X509Certificate[] chain, string authType) { }
public override X509Certificate[] GetAcceptedIssuers() => Array.Empty<X509Certificate>();
}
public class DangerousTrustManagerFactory : TrustManagerFactorySpi
{
protected override void EngineInit(IManagerFactoryParameters mgrparams) { }
protected override void EngineInit(KeyStore keystore) { }
protected override ITrustManager[] EngineGetTrustManagers() => new ITrustManager[] { new DangerousTrustManager() };
public static string GetAlgorithm() => TRUST_PROVIDER_ALG;
}
}
}
If you also want to disable host name verfication, you'll need to dynamically inherit from AndroidMessageHandler and override its internal GetSSLHostnameVerifier method, to return a dummy IHostNameVerifier:
using Javax.Net.Ssl;
using System.Reflection;
using System.Reflection.Emit;
using Xamarin.Android.Net;
namespace MyApp.Platforms.Android
{
static class DangerousAndroidMessageHandlerEmitter
{
private static Assembly _emittedAssembly = null;
public static void Register(string handlerTypeName = "DangerousAndroidMessageHandler", string assemblyName = "DangerousAndroidMessageHandler")
{
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
if (e.Name == assemblyName)
{
if (_emittedAssembly == null)
{
_emittedAssembly = Emit(handlerTypeName, assemblyName);
}
return _emittedAssembly;
}
return null;
};
}
private static AssemblyBuilder Emit(string handlerTypeName, string assemblyName)
{
var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
var module = assembly.DefineDynamicModule(assemblyName);
DefineDangerousAndroidMessageHandler(module, handlerTypeName);
return assembly;
}
private static void DefineDangerousAndroidMessageHandler(ModuleBuilder module, string handlerTypeName)
{
var typeBuilder = module.DefineType(handlerTypeName, TypeAttributes.Public);
typeBuilder.SetParent(typeof(AndroidMessageHandler));
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);
var methodBuilder = typeBuilder.DefineMethod(
"GetSSLHostnameVerifier",
MethodAttributes.Public | MethodAttributes.Virtual,
typeof(IHostnameVerifier),
new[] { typeof(HttpsURLConnection) }
);
var generator = methodBuilder.GetILGenerator();
generator.Emit(OpCodes.Call, typeof(DangerousHostNameVerifier).GetMethod("Create"));
generator.Emit(OpCodes.Ret);
typeBuilder.CreateType();
}
}
public class DangerousHostNameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify(string hostname, ISSLSession session)
{
return true;
}
public static IHostnameVerifier Create() => new DangerousHostNameVerifier();
}
}
Call DangerousAndroidMessageHandlerEmitter.Register and DangerousTrustProvider in your MauiProgram:
#if ANDROID && DEBUG
Platforms.Android.DangerousAndroidMessageHandlerEmitter.Register();
Platforms.Android.DangerousTrustProvider.Register();
#endif
One last step, you need to tell Xamarin to use your dynamically generated DangerousAndroidMessageHandler. You should be able to do so by setting AndroidHttpClientHandlerType to fully-qualified name of the handler type in your csproj file:
<PropertyGroup>
<AndroidHttpClientHandlerType>DangerousAndroidMessageHandler, DangerousAndroidMessageHandler</AndroidHttpClientHandlerType>
</PropertyGroup>
Or set Android runtime environment variable XA_HTTP_CLIENT_HANDLER_TYPE to the name of the handler:
XA_HTTP_CLIENT_HANDLER_TYPE=DangerousAndroidMessageHandler, DangerousAndroidMessageHandler
The above workaround will also work for ClientWebSocket and anything else using SslStream. Which means you can connect to your test SignalR server with the WebSocket transport (which is what I was trying to achieve).
Just remember, DO THIS ONLY IN DEBUG BUILDS.
In MainApplication.cs for the Android platform:
#if DEBUG
[Application(AllowBackup = false, Debuggable = true, UsesCleartextTraffic = true)]
#else
[Application]
#endif
public class MainApplication : MauiApplication
In ASP.NET Core API Program.cs:
#if !DEBUG
app.UseHttpsRedirection();
#endif
In MauiProgram.cs:
#if DEBUG
private static readonly string Base = "http://192.168.0.15";
private static readonly string ApiBaseUrl = $"{Base}:5010/";
#else
private static readonly string ApiBaseUrl = "https://YOUR_APP_SERVICE.azurewebsites.net/";
#endif
...
builder.Services.AddSingleton(sp => new HttpClient { BaseAddress = new Uri(ApiBaseUrl) });
In ASP.NET Core API launchSettings.json:
"applicationUrl": "https://*:5011;http://*:5010"
Intro
Since some "super-clever" SO reviewers thought it would - quote -
defaces the post in order to promote a product or service, or is deliberately destructive
if nolex's answer gets edited to
fix a bug causing her/his solution to fail in latest MAUI (using VS version 17.2 Preview 2.1)
remove unnecessary / obsolete stuff from her/his code to
simplify it by using C# syntax sugar available since at least C# 10.0, if not already 9.0
I'm posting the updated code as a separate answer.
The issue
As nolex already pointed out in his answer, the HttpClientHandler actually uses AndroidMessageHandler as its underlying handler - which does implemented the known ServerCertificateCustomValidationCallback.
However, its value is never used when sending requests which you can easily verify yourself by searching the linked source code file for another occurrence of that property.
There's even a pull request waiting for (further) approval & merge since February 11th this year to solve this. But even after the latest resolve just 17 days ago as of today, it's still not merged. Plus, 5 checks are failing now - again.
The only workaround - for the time being that is
If you desire (or even require) to run your (debug) server build on the same machine your Android Emulator runs on & a secure connection between them is required, there's only way for you: overwrite Android's default TrustManager with your own DangerousTrustManager. This allows your app to bypass any certificate verification, hence the prefix Dangerous. 😉
I can't stress that enough, so again: do not use this workaround's code beyond locally running debug builds. Not on testing environments. Not on staging environments. Seriously!
Though, there's also a goodie here: this workaround allows any connection attempt using SslStream, e. g. ClientWebSocket, to succeed. Therefore, your local SignalR server's WebSocket transport will work as well!
Notes regarding code below:
As I enabled Nullable for the whole MAUI project you'll see ? suffixes on strings & the like.
I can't stand horizontal code scrolling anywhere, hence excessive usage of line breaks.
Alright, let's get into it:
MyMauiApp\Platforms\Android\DangerousTrustProvider.cs:
#if DEBUG // Ensure this never leaves debug stages.
using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;
namespace MyMauiApp.Platforms.Android;
internal class DangerousTrustProvider : Provider
{
private const string DANGEROUS_ALGORITHM = nameof(DANGEROUS_ALGORITHM);
// NOTE: Empty ctor, i. e. without Put(), works for me as well,
// but I'll keep it for the sake of completeness.
public DangerousTrustProvider()
: base(nameof(DangerousTrustProvider), 1, "Dangerous debug TrustProvider") =>
Put(
$"{nameof(DangerousTrustManagerFactory)}.{DANGEROUS_ALGORITHM}",
Java.Lang.Class.FromType(typeof(DangerousTrustManagerFactory)).Name);
public static void Register()
{
if (Security.GetProvider(nameof(DangerousTrustProvider)) is null)
{
Security.InsertProviderAt(new DangerousTrustProvider(), 1);
Security.SetProperty(
$"ssl.{nameof(DangerousTrustManagerFactory)}.algorithm", DANGEROUS_ALGORITHM);
}
}
public class DangerousTrustManager : X509ExtendedTrustManager
{
public override void CheckClientTrusted(X509Certificate[]? chain, string? authType) { }
public override void CheckClientTrusted(X509Certificate[]? chain, string? authType,
Socket? socket) { }
public override void CheckClientTrusted(X509Certificate[]? chain, string? authType,
SSLEngine? engine) { }
public override void CheckServerTrusted(X509Certificate[]? chain, string? authType) { }
public override void CheckServerTrusted(X509Certificate[]? chain, string? authType,
Socket? socket) { }
public override void CheckServerTrusted(X509Certificate[]? chain, string? authType,
SSLEngine? engine) { }
public override X509Certificate[] GetAcceptedIssuers() =>
Array.Empty<X509Certificate>();
}
public class DangerousTrustManagerFactory : TrustManagerFactorySpi
{
protected override ITrustManager[] EngineGetTrustManagers() =>
new[] { new DangerousTrustManager() };
protected override void EngineInit(IManagerFactoryParameters? parameters) { }
protected override void EngineInit(KeyStore? store) { }
}
}
#endif
Since Android performs additional hostname verification, dynamically inheriting AndroidMessageHandler in order to override its internal GetSSLHostnameVerifier method by returning a dummy IHostNameVerifier is required, too.
MyMauiApp\Platforms\Android\DangerousAndroidMessageHandlerEmitter.cs:
#if DEBUG // Ensure this never leaves debug stages.
using System.Reflection;
using System.Reflection.Emit;
using Javax.Net.Ssl;
using Xamarin.Android.Net;
namespace MyMauiApp.Platforms.Android;
internal static class DangerousAndroidMessageHandlerEmitter
{
private const string NAME = "DangerousAndroidMessageHandler";
private static Assembly? EmittedAssembly { get; set; } = null;
public static void Register(string handlerName = NAME, string assemblyName = NAME) =>
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
assemblyName.Equals(args.Name)
? (EmittedAssembly ??= Emit(handlerName, assemblyName))
: null;
private static AssemblyBuilder Emit(string handlerName, string assemblyName)
{
var assembly = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
var builder = assembly.DefineDynamicModule(assemblyName)
.DefineType(handlerName, TypeAttributes.Public);
builder.SetParent(typeof(AndroidMessageHandler));
builder.DefineDefaultConstructor(MethodAttributes.Public);
var generator = builder.DefineMethod(
"GetSSLHostnameVerifier",
MethodAttributes.Public | MethodAttributes.Virtual,
typeof(IHostnameVerifier),
new[] { typeof(HttpsURLConnection) })
.GetILGenerator();
generator.Emit(
OpCodes.Call,
typeof(DangerousHostNameVerifier)
.GetMethod(nameof(DangerousHostNameVerifier.Create))!);
generator.Emit(OpCodes.Ret);
builder.CreateType();
return assembly;
}
public class DangerousHostNameVerifier : Java.Lang.Object, IHostnameVerifier
{
public bool Verify(string? hostname, ISSLSession? session) => true;
public static IHostnameVerifier Create() => new DangerousHostNameVerifier();
}
}
#endif
As a second last step, the newly created types need to be registered for Android MAUI debug builds.
MyMauiApp\MauiProgram.cs:
namespace MyMauiApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>()
.ConfigureFonts(fonts => fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"));
builder.Services.AddTransient(provider => new HttpClient
{
BaseAddress = new Uri($#"https://{(DeviceInfo.DeviceType == DeviceType.Virtual
? "10.0.2.2" : "localhost")}:5001/"),
Timeout = TimeSpan.FromSeconds(10)
});
#if ANDROID && DEBUG
Platforms.Android.DangerousAndroidMessageHandlerEmitter.Register();
Platforms.Android.DangerousTrustProvider.Register();
#endif
return builder.Build();
}
}
Finally, for MAUI / Xamarin to really use the dynamically generated DangerousAndroidMessageHandler, an AndroidHttpClientHandlerType property inside the MyMauiApp.csproj file, containing twice the handler's name, is required.
MyMauiApp\Platforms\Android\MyMauiApp.csproj:
<PropertyGroup>
<AndroidHttpClientHandlerType>DangerousAndroidMessageHandler, DangerousAndroidMessageHandler</AndroidHttpClientHandlerType>
</PropertyGroup>
Alternatively, setting the Android runtime environment variable XA_HTTP_CLIENT_HANDLER_TYPE to the same value works as well:
XA_HTTP_CLIENT_HANDLER_TYPE=DangerousAndroidMessageHandler, DangerousAndroidMessageHandler
Outro
Until the official fix arrives, remember: for the sake of this world's security, do not use this in production!
Now go, chase that (app) dream of yours 🥳
If we're forced to implement classes that override the certificate verification, with emphasis on this never leaving the development environment, might as well do the bad thing... with fewer lines of code.
Just change https to http.
In the client project change the URL of your API to http and add android:usesCleartextTraffic="true" in AndroidManifest.xml.
In your server project comment out line app.UseHttpsRedirection();
This is terrible and I hope it will be fixed soon.
Alternative to ignoring all certificates is to install certificate on your dev device yourself, it will also workaround MAUI/Xamarin issue with ServerCertificateCustomValidationCallback for android SSL connections. For iOS it works out of the box, for Android you need to allow app to use user certificates as described here: How to install trusted CA certificate on Android device?

Servicestack Test: Method not found: 'Int32 ServiceStack.DataAnnotations.CustomFieldAttribute.get_Order()

Trying to build integration test with connection to db in ServiceStack.
My ServiceStack app is working fine, but when I run simple test I got this error message in line:22
System.MissingMethodException: 'Method not found: 'Int32 ServiceStack.DataAnnotations.CustomFieldAttribute.get_Order()'.'
There is a lite cod:
using ServiceStack;
using ServiceStack.OrmLite;
using ServiceStack.Data;
using NUnit.Framework;
using ServiceStack.DataAnnotations;
using System.Collections.Generic;
namespace oth.Tests.IntegrationTests
{
public class AppHost2 : AppSelfHostBase
{
public AppHost2() : base("Customer REST Example", typeof(CustomerService).Assembly) { }
public override void Configure(Container container)
{
var connectionString = "Host=localhost;Port=5432;Database=test_1234;Username=postgres;Password=local";
container.Register<IDbConnectionFactory>(c =>
new OrmLiteConnectionFactory(connectionString, PostgreSqlDialect.Provider));
using var db = container.Resolve<IDbConnectionFactory>().Open();
db.CreateTableIfNotExists<Customer>();
}
}
public class Customer
{
[AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
}
[Route("/customers", "GET")]
public class GetCustomers : IReturn<GetCustomersResponse> { }
public class GetCustomersResponse
{
public List<Customer> Results { get; set; }
}
public class CustomerService : Service
{
public object Get(GetCustomers request)
{
return new GetCustomersResponse { Results = Db.Select<Customer>() };
}
}
public class CustomerRestExample
{
const string BaseUri = "http://localhost:2000/";
ServiceStackHost appHost;
public CustomerRestExample()
{
//Start your AppHost on TestFixture SetUp
appHost = new AppHost2()
.Init()
.Start(BaseUri);
}
[OneTimeTearDown]
public void OneTimeTearDown() => appHost.Dispose();
/* Write your Integration Tests against the self-host instance */
[Test]
public void Run_Customer_REST_Example()
{
var client = new JsonServiceClient(BaseUri);
var all = client.Get(new GetCustomers());
Assert.That(all.Results.Count, Is.EqualTo(0));
}
}
}
Anytime you see a missing type or missing method exceptions when using the MyGet pre-release packages it means you have a dirty installation (i.e. using pre-release packages from different build times).
In which case you'd need to Clear your Nuget packages cache and download the latest packages again, which ensures all your packages are from the latest same build:
$ dotnet nuget locals all -clear

Debugger skipping methods after organizing code into new solutions

I had a perfectly working API that was making http calls & a UI that was using that API.
Everything was working and it was built really ugly ( 2 solutions for everything), so I wanted to separate everything so it would be more organized.
UI
DataManagerService
DataManager
Contracts
After a lot of copy paste & dependencies references it all looked like its working with 0 errors.
BUT now something weird happens, A method is being skipped and I have NO IDEA WHY.
I am not on release mode.
If anyone has any ideas I would appreciate it!
The method that is being skipped :
private static List<Actor> ReadActorsFromJson(string json)
{
List<Actor> celebListReadFromFile;
try
{
var celebJson = File.ReadAllText(json);
celebListReadFromFile = JsonConvert.DeserializeObject<List<Actor>>(celebJson);
}
catch (Exception ex)
{
celebListReadFromFile = new List<Actor>();
// Empty list/whatever it got in it
}
return celebListReadFromFile;
}
Which is being invoked by :
public static async Task SaveOriginal()
{
foreach (var currceleb in ReadActorsFromJson(filePath))
{
var curr = currceleb;
originalList.TryAdd(currceleb.name, currceleb);
}
}
and this method is being invoked by the classes static constructor:
static Logic()
{
originalList = new ConcurrentDictionary<string, Actor>();
filePath = ConfigurationManager.AppSettings["tempList"];
File.Copy(filePath, BACKUP, true);
// invoking the method
SaveOriginal();
}
The API:
using Contracts;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace WebApplication12.Controllers
{
public class ValuesController : Controller
{
public ILogic _Ilogic;
public ValuesController(ILogic logic)
{
_Ilogic = logic;
}
// GET api/values
public async Task<ActionResult> GetActors()
{
return Json(await _Ilogic.GetAllActorsAsync(), JsonRequestBehavior.AllowGet);
}
public async Task<ActionResult> RemoveActorAsync(Actor actor) {
await _Ilogic.RemoveActorAsync(actor.name);
return Json(await _Ilogic.GetAllActorsAsync());
}
public async Task<ActionResult> ResetAsync()
{
await _Ilogic.ResetAsync();
return Json(await _Ilogic.GetAllActorsAsync());
}
}
}
The business logic :
using System;
using System.Collections.Generic;
using System.IO;
using System.Configuration;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Linq;
using Newtonsoft.Json;
using Contracts;
namespace DataManager
{
public class Logic : ILogic
{
static string filePath;
private static ConcurrentDictionary<string, Actor> originalList;
const string BACKUP = #"C:\tempList\backup.txt";
static Logic()
{
originalList = new ConcurrentDictionary<string, Actor>();
filePath = ConfigurationManager.AppSettings["tempList"];
File.Copy(filePath, BACKUP, true);
SaveOriginal();
}
public async static Task<List<Actor>> GetCelebritiesInner()
{
return originalList.Values.ToList();
}
public async Task<List<Actor>> GetAllActorsAsync()
{
return await GetCelebritiesInner();
}
// Try to read the data from the Json and initialize it. if failed , initialize with whatever it got. return
private static List<Actor> ReadActorsFromJson(string json)
{
List<Actor> celebListReadFromFile;
try
{
var celebJson = File.ReadAllText(json);
celebListReadFromFile = JsonConvert.DeserializeObject<List<Actor>>(celebJson);
}
catch (Exception ex)
{
celebListReadFromFile = new List<Actor>();
// Empty list/whatever it got in it
}
return celebListReadFromFile;
}
public async Task RemoveActorAsync(string name)
{
if (originalList.TryRemove(name, out Actor removedActor))
{
var jsonToWrite = JsonConvert.SerializeObject(await GetCelebritiesInner());
try
{
File.WriteAllText(filePath, jsonToWrite);
}
catch (Exception ex)
{
//Unable to remove due to an error.
}
}
}
public async Task ResetAsync()
{
originalList.Clear();
await UpdateFile();
await SaveOriginal();
}
//Saving the actor, adding the name as key & object as value.
public static async Task SaveOriginal()
{
foreach (var currceleb in ReadActorsFromJson(filePath))
{
var curr = currceleb;
originalList.TryAdd(currceleb.name, currceleb);
}
}
public static async Task UpdateFile()
{
File.Copy(BACKUP, filePath, true);
}
}
}
When running the program, the static ctor is being invoked and should invoke the SaveOriginal method. which it doesn't.
Static constructor calls when the first access is made. You are using dependency injection which is lazy loading. It doesn't create object until first access is made.
So, Try to get/set any property or method, static constructor will be called first.
Ok so I cleaned and re built every solution one by one ( I don't know if it helped) & then I ran the API, sent a request from POSTMAN instead of opening the UI, in postman I got the following error:
Could not load file or assembly 'Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies
I looked at the versions in the Nuget packages and there was no 12.0.0.0 in there, so I removed Newtonsoft.Json from all solutions, re-installed Newtonsoft.Json in every solution and it worked.
turns out somehow when opening the new solutions, I installed different Newtonsoft.Json versions. and this caused the program to skip the method without giving the stack trace like postman did, weird.

MassTransit - PublishFault stops working when Message Q is down

I am connecting a publish observer, using the code below (and RabbitMQ). When RabbitMQ is running, it works well - I can see the console messages on PostPublish and PrePublish.
However, when i stop RabbitMQ, and publish, the PublishFault works once, but never again whilst RabbitMQ remains stopped.
I am attempting to persist a message to another datastore (and log and error), in the event of a publish failing - I thought that the PublishFault method would be the best place to do this. This doesn't really work if only the first failure is detected.
Is this behaviour expected? Is there a better way to achieve failed message persistance.
PS...as soon as I start RabbitMQ again, I then see all my PrePublish and PostPublish debug messages , for my failed messages. Which, I assume, is to be expected.
using MassTransit;
using MassTransit.Pipeline;
using System;
using System.Threading.Tasks;
namespace Mtt.Publisher
{
class Program
{
static void Main(string[] args)
{
IBusControl busControl = Bus.Factory.CreateUsingRabbitMq(sbc =>
{
var host = sbc.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("user");
h.Password("pass");
});
sbc.UseRetry(Retry.Immediate(5));
});
busControl.Start();
busControl.ConnectPublishObserver(new PublishObserver());
var input = "";
while (input != "exit")
{
input = Console.ReadLine();
busControl.Publish<Test>(new TestMessage());
}
busControl.Stop();
}
}
public interface Test { }
public class TestMessage : Test { }
public class PublishObserver : IPublishObserver
{
public async Task PostPublish<T>(MassTransit.PublishContext<T> context) where T : class
{
Console.WriteLine("--- POST PUBLISH ----");
}
public async Task PrePublish<T>(MassTransit.PublishContext<T> context) where T : class
{
Console.WriteLine("**** PRE PUBLISH ****");
}
public async Task PublishFault<T>(MassTransit.PublishContext<T> context, Exception exception) where T : class
{
Console.WriteLine("%%%%%% EXCEPTION %%%%%%%");
}
}
}

HubConnection.Start throws error only when called from singleton object

I have built a notification system with the following code:
class SignalRClient
{
HubConnection hubconn;
IHubProxy proxy;
public SignalRClient(string url)
{
hubconn = new HubConnection(url);
proxy = hubconn.CreateProxy("XXX.NotificationHub");
hubconn.Start().Wait();
}
public void SendMessage()
{
proxy.Invoke("LiveNotify", new { Application = "SRWinClient", Server = Environment.MachineName, Message = "This is a test", ImgId= 2 });
}
}
This works great when i trigger it from a test windows forms app (on a button click), but when i call if from a singleton object that i have it fails on the Start().Wait(). I have copy pasted the code and checked a number of times to make sure there were no typos.
Here is my notification singleton. It does more than the SignalR bit. it updates Databases and more, but here is the relevant parts:
public class CDSNotifier
{
private static object mLock = new object();
private static CDSNotifier mnotifier = null;
private bool enableSignalRNotifications = false;
private SignalRNotifier snotifier;
private CDSNotifier()
{
NameValueCollection appSettings = ConfigurationManager.AppSettings;
try
{
enableSignalRNotifications = Convert.ToBoolean(appSettings["EnableSignalRNotifications"]);
}
catch { };
if (enableSignalRNotifications)
{
snotifier = new SignalRNotifier(appSettings["SignalRHubURL"]);
}
}
public static CDSNotifier Instance
{
get
{
if (mnotifier == null)
{
// for thread safety, lock an object when
lock (mLock)
{
if (mnotifier == null)
{
mnotifier = new CDSNotifier();
}
}
}
return mnotifier;
}
}
public void Notify(int applicationId, int? companyId, string message, NotificationType type, string server)
{
.....
try
{
if (enableSignalRNotifications)
snotifier.SendMessage(applicationId, message, type);
}
catch { }
}
Exception that I am getting:
System.AggregateException
Message: One or more errors occured
StackTrace: at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
at System.Threading.Tasks.Task.Wait()
I finally figured it out. My notification system was a separate lib and my executable's bin was not getting the Newtonsoft.JSON dlls. I added the package using nuget to my primary projects and it worked like a charm.
#M.Babcock thanks for leading me in the right direction. i looked at the exceptions but i was looking at the one that said "InnerExceptions" (the one with s), that did not have any info. but when i looked in to "InnerException" i found more info.

Categories