How to enable search for a RadioGroup DVC in MonoTouch.Dialog? - search

I have a RadioGroup with very many RadioElements as a Sub-DialogViewController:
Root.Add(
new Section() {
new RootElement ("Demo", new RadioGroup ("demogroup", 0)) {
new Section () {
from demoItem in bigItemList
select (Element) new RadioElement (demoItem)
}
}
}
);
I want to enable Search for this nested DVC to make picking the right RadioElement simpler. Therefor I implemented a custom RootElement which combines passing a Group and creating a DVC with EnableSearch and used it instead of the one above:
using System.Collections.Generic;
namespace MonoTouch.Dialog
{
public class SearchableRootElement : RootElement
{
public SearchableRootElement(string caption, Group group) : base(caption, group)
{
this.createOnSelected = x => {
return new DialogViewController(x) { EnableSearch = true };
};
}
}
}
Unfortunately when typing into the Searchbar of the sub DVC I get the following crash:
Unhandled Exception:
System.NullReferenceException: Object reference not set to an instance of an object
at MonoTouch.Dialog.RadioElement.GetCell (MonoTouch.UIKit.UITableView tv) [0x00019] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/Elements.cs:1066
at MonoTouch.Dialog.DialogViewController+Source.GetCell (MonoTouch.UIKit.UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) [0x00029] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/DialogViewController.cs:341
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at Demo.iOS.Application.Main (System.String[] args) [0x00001] in /Users/rodjatrappe/Projects/Claas/Dev/Apps/Demo.iOS/Main.cs:16
2013-06-22 14:15:02.296 DemoiOS[547:21b03] Unhandled managed exception: Object reference not set to an instance of an object (System.NullReferenceException)
at MonoTouch.Dialog.RadioElement.GetCell (MonoTouch.UIKit.UITableView tv) [0x00019] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/Elements.cs:1066
at MonoTouch.Dialog.DialogViewController+Source.GetCell (MonoTouch.UIKit.UITableView tableView, MonoTouch.Foundation.NSIndexPath indexPath) [0x00029] in /Developer/MonoTouch/Source/MonoTouch.Dialog/MonoTouch.Dialog/DialogViewController.cs:341
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr)
at MonoTouch.UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at Demo.iOS.Application.Main (System.String[] args) [0x00001] in /Users/rodjatrappe/Projects/Claas/Dev/Apps/Demo.iOS/Main.cs:16
Why is it crashing and how to archive the feature I described above?

I cannot give you a direct answer but you may (assuming your source is not out of sync with this one) want to have a look at Line 1066 -
https://github.com/migueldeicaza/MonoTouch.Dialog/blob/master/MonoTouch.Dialog/Elements.cs
if (!(root.group is RadioGroup))
Is root null? Consider downloading the MTD source code and debugging it, check how you are creating your DVC.
You could also replace your LINQ with a couple of hard coded sections, ensure that is not your issue.
Hope this helps

The bug report here includes a workaround for the root cause of the issue you're experiencing, but also talks about how filtering will then cause a usability issue of marking the nth element as selected even after a filter has been applied.
https://github.com/migueldeicaza/MonoTouch.Dialog/issues/203
If you don't want to update the core MTD code, you could use that same technique by putting it in your own UIBarSearchDelegate. Unfortunately, the default SearchDelegate class is internal, so you'll need to add all of the code in your delegate. I was able to do this and get it working without changing the MTD source:
public override void LoadView()
{
base.LoadView();
((UISearchBar)TableView.TableHeaderView).Delegate = new MySearchBarDelegate(this);
}
And then you use this instead of the base method:
public override void TextChanged (UISearchBar searchBar, string searchText)
{
container.PerformFilter (searchText ?? "");
foreach (var s in container.Root)
s.Parent = container.Root;
}

Related

UI not rendering after navigation after i call a method from MSAL2.5

i am trying to make Azure AD B2C work in my xamarin app. to check if we already are logged in i am using a code like below.
public async Task<bool> IsLoggedIn()
{
IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync();
if (accounts != null)
{
return accounts.Any();
}
return false;
}
}
My code on Start looks like this
protected async override void OnStart()
{
var authenticationService = Container.Resolve<IAuthenticationService>();
bool isLoggedIn;
isLoggedIn = await authenticationService.IsLoggedIn();
if (isLoggedIn)
{
var cachingService = Container.Resolve<ICachingService>();
AppUser = await authenticationService.Login(cachingService.CurrentMode);
await NavigationService.NavigateAsync("http://www.xxxx.com/root/navigation/main");
}
else
{
await NavigationService.NavigateAsync("http://www.xxxx.com/navigation/login");
}
}
}
if i just return false from IsLoggedIn it shows the login page properly.
But calling
IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync();
seems to be problematic.
i get exception (sometimes) that
System.NullReferenceException: Object reference not set to an instance of an object
but looking at App object in "QuickWatch" i see PAC object being properly populated.
UPDATE
App.cs constructor looks like this
public static PublicClientApplication PCA = null;
public App(IPlatformInitializer initializer = null) : base(initializer)
{
PCA = new PublicClientApplication(GlobalSetting.Instance.ClientID, GlobalSetting.Instance.AuthoritySignUpSignIn);
PCA.RedirectUri = $"msal{GlobalSetting.Instance.ClientID}://auth";
}
my login method looks like this
public async Task<User> Login(string mode)
{
IEnumerable<IAccount> accounts = await App.PCA?.GetAccountsAsync();
AuthenticationResult ar;
try
{ //get token from cache.
ar = await App.PCA?.AcquireTokenSilentAsync(GlobalSetting.Instance.Scopes, GetAccountByPolicy(accounts, GlobalSetting.Instance.PolicySignUpSignIn), GlobalSetting.Instance.AuthoritySignUpSignIn, false);
}
catch (MsalUiRequiredException ex)
{
// get token from interaction.
ar = await App.PCA?.AcquireTokenAsync(GlobalSetting.Instance.Scopes, GetAccountByPolicy(accounts, GlobalSetting.Instance.PolicySignUpSignIn), App.UiParent);
}
//fetch token and make actual user object.
return new User
{
Id = _cachingService.LoggedInUserId,
Name = "Jessica Doe",
ProfilePicUrl = "https://content-static.upwork.com/uploads/2014/10/01xxx27/profilephoto1.jpg",
BusinessProfile = _userService.GetBusinessProfile(_cachingService.LoggedInUserId),
ProfileVariables = _userService.GetUserProfileVariables(_cachingService.LoggedInUserId),
Settings = _userService.GetSettings(_cachingService.LoggedInUserId)
};
}
it works when i dont call AcquireTokenSilentAsync and just send a fake User object and does not work when i call AcquireTokenSilentAsync.
ar object gets populated, it navigates to the main page hits it's view model constructor but but shows a blank page.
i have also tried diffrent versions of MSAL. now on the latest version.
UPdate2
drilled down to this exception
{Microsoft.Identity.Client.MsalClientException: The application does not have keychain access groups enabled in the Entitlements.plist. As a result, there was a failure to save to the iOS keychain. The keychain access group '3G3LMCD5R.com.microsoft.adalcache' is not enabled in the Entitlements.plist. See https://aka.ms/msal-net-enable-keychain-groups for more details on enabling keychain access groups and entitlements. at Microsoft.Identity.Core.iOSTokenCacheAccessor.Save (System.String account, System.String service, System.String generic, System.Int32 type, System.String value) [0x00052] in :0 at Microsoft.Identity.Core.iOSTokenCacheAccessor.SaveAccessToken (Microsoft.Identity.Core.Cache.MsalAccessTokenCacheItem item) [0x00028] in :0 at Microsoft.Identity.Core.Telemetry.TelemetryTokenCacheAccessor.SaveAccessToken (Microsoft.Identity.Core.Cache.MsalAccessTokenCacheItem item) [0x00000] in :0 at Microsoft.Identity.Core.Telemetry.TelemetryTokenCacheAccessor.SaveAccessToken (Microsoft.Identity.Core.Cache.MsalAccessTokenCacheItem item, Microsoft.Identity.Core.RequestContext requestContext) [0x0002a] in :0 at Microsoft.Identity.Client.TokenCache.SaveAccessAndRefreshToken (Microsoft.Identity.Core.Instance.IValidatedAuthoritiesCache validatedAuthoritiesCache, Microsoft.Identity.Core.Instance.IAadInstanceDiscovery aadInstanceDiscovery, Microsoft.Identity.Client.Internal.Requests.AuthenticationRequestParameters requestParams, Microsoft.Identity.Core.OAuth2.MsalTokenResponse response) [0x00143] in :0 at Microsoft.Identity.Client.Internal.Requests.RequestBase.CacheTokenResponseAndCreateAuthenticationResult (Microsoft.Identity.Core.OAuth2.MsalTokenResponse msalTokenResponse) [0x001b4] in :0 at Microsoft.Identity.Client.Internal.Requests.InteractiveRequest+d__9.MoveNext () [0x00168] in :0 --- End of stack trace from previous location where exception was thrown --- at Microsoft.Identity.Client.Internal.Requests.RequestBase+d__28.MoveNext () [0x00160] in :0 --- End of stack trace from previous location where exception was thrown --- at Microsoft.Identity.Client.PublicClientApplication+d__24.MoveNext () [0x000ef] in :0 --- End of stack trace from previous location where exception was thrown --- at Microsoft.Identity.Client.PublicClientApplication+d__17.MoveNext () [0x000ac] in :0 --- End of stack trace from previous location where exception was thrown --- at CDThat.Services.AzureADB2CAuthenticationService+d__3.MoveNext () [0x000fc] in C:\CDthatbest\CDThat\Services\AzureADB2CAuthenticationService.cs:45 --- End of stack trace from previous location where exception was thrown --- at CDThat.ViewModels.LoginPageViewModel+d__8.MoveNext () [0x0003c] in C:\CDthatbest\CDThat\ViewModels\LoginPageViewModel.cs:56 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.b__6_0 (System.Object state) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.2.1.11/src/Xamarin.iOS/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1023 at Foundation.NSAsyncSynchronizationContextDispatcher.Apply () [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.2.1.11/src/Xamarin.iOS/Foundation/NSAction.cs:178 at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr) at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.2.1.11/src/Xamarin.iOS/UIKit/UIApplication.cs:79 at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x0002c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/12.2.1.11/src/Xamarin.iOS/UIKit/UIApplication.cs:63 at CDThat.iOS.Application.Main (System.String[] args) [0x00014] in C:\CDthatbest\CDThat.iOS\Main.cs:16 ErrorCode: missing_entitlements}
It might be a timing issue (accessing the PCA property to early). I would first change the code to handle the empty PCA property more gracefully:
public async Task<bool> IsLoggedIn()
{
// Added a null check on the PCA property
IEnumerable<IAccount> accounts = await App.PCA?.GetAccountsAsync();
if (accounts != null)
{
return accounts.Any();
}
return false;
}
}
And then try a different event (such as PageAppearing) instead of OnStart to access the PCA property.
Hope it helps!

Xamarin.UITest System.Xml.XmlException

I am creating a UI test for my Xamarin.Forms app. When I try to start the first test for iOS project, I get a System.Xml.XmlException (detailed below). This basic test should be working, right?
Update: If I uninstall the app from the simulator, the test runs for the first time. After that I keep getting the exception for all following runs.
I am using Xamarin Studio (6.1.3 build 19) and
Xamarin.UITest package (2.0.5)
namespace UITest
{
[TestFixture (Platform.Android)]
[TestFixture (Platform.iOS)]
public class Tests
{
IApp app;
Platform platform;
public Tests (Platform platform)
{
this.platform = platform;
}
[SetUp]
public void BeforeEachTest ()
{
app = AppInitializer.StartApp (platform);
}
[Test]
public void AppLaunches ()
{
try {
app.WaitForElement ("Waiting for fake element", "Timeout", new TimeSpan (0, 0, 20));
} catch (TimeoutException ex) {
int i = 5;
}
app.Screenshot ("Welcome screen.");
app.Tap ("TestButton");
app.Screenshot ("New test screen.");
}
}
public class AppInitializer
{
public static IApp StartApp (Platform platform)
{
if (platform == Platform.Android) {
return ConfigureApp
.Android
.StartApp ();
}
return ConfigureApp
.iOS
.Debug ()
.EnableLocalScreenshots ()
.StartApp ();
}
}
}
Exception details:
SetUp : System.Xml.XmlException : '', hexadecimal value 0x01, is an invalid character. Line 11, position 11.
Stack trace:
at Xamarin.UITest.iOS.iOSAppLauncher.LaunchAppLocal (Xamarin.UITest.Configuration.IiOSAppConfiguration appConfiguration, Xamarin.UITest.Shared.Http.HttpClient httpClient, System.Boolean clearAppData) [0x0020a] in <e747267e258a4a668973c7ca7e9014a8>:0
at Xamarin.UITest.iOS.iOSAppLauncher.LaunchApp (Xamarin.UITest.Configuration.IiOSAppConfiguration appConfiguration, Xamarin.UITest.Shared.Http.HttpClient httpClient, Xamarin.UITest.TestCloud.TestCloudiOSAppConfiguration testCloudAppConfiguration, Xamarin.UITest.Shared.Http.HttpClient testCloudWsClient, Xamarin.UITest.Shared.Http.HttpClient xtcServicesClient, System.Boolean testCloudUseDeviceAgent) [0x0007a] in <e747267e258a4a668973c7ca7e9014a8>:0
at Xamarin.UITest.iOS.iOSApp..ctor (Xamarin.UITest.Configuration.IiOSAppConfiguration appConfiguration) [0x00302] in <e747267e258a4a668973c7ca7e9014a8>:0
at Xamarin.UITest.Configuration.iOSAppConfigurator.StartApp (Xamarin.UITest.Configuration.AppDataMode appDataMode) [0x00017] in <e747267e258a4a668973c7ca7e9014a8>:0
at UITest.AppInitializer.StartApp (Xamarin.UITest.Platform platform) [0x0001f] in /Users/samg/projects/myapp-mobile/myapp-mobile-2/UITest/AppInitializer.cs:38
at UITest.Tests.BeforeEachTest () [0x00008] in /Users/samg/projects/myapp-mobile/myapp-mobile-2/UITest/Tests.cs:26
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00038] in /private/tmp/source-mono-4.6.0/bockbuild-mono-4.6.0-branch/profiles/mono-mac-xamarin/build-root/mono-x86/mcs/class/corlib/System.Reflection/MonoMethod.cs:305
I know this is an old question, however it might help others. I experienced the same problem described above and solved it by resetting the iOS simulator.
One way to accomplish this is to take the following steps:
Make sure the iPhone/iPad simulator is running;
Go to the top-menu and select "iOS Simulator" -> "Reset Content and Settings..."

Exception thrown when using Glimpse and Postal

I'm just starting to use Glimpse with my MVC5 project and have run into an issue when I use Postal to send an email without disabling Glimpse. I've been able to narrow it down to an issue with both packages - it doesn't occur if the Glimpse cookie has not been turned on.
In Fiddler, I checked the difference between the two. When it threw the exception, the cookie was
glimpsePolicy=On
when it worked (Glimpse was off) there were two cookies
glimpseId=FBar; glimpsePolicy=
The exception I get is
System.ArgumentNullException: Value cannot be null.
Parameter name: controllerContext
at System.Web.Mvc.ChildActionValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
at Castle.Proxies.Invocations.ValueProviderFactory_GetValueProvider.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Glimpse.Core.Extensibility.ExecutionTimer.Time(Action action)
at Glimpse.Core.Extensibility.AlternateMethod.NewImplementation(IAlternateMethodContext context)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.ValueProviderFactoryProxy.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ControllerBase.get_ValueProvider()
at Glimpse.Mvc.Message.ActionMessageExtension.AsActionMessage[T](T message, ControllerBase controller)
at Glimpse.Mvc.AlternateType.ViewEngine.FindViews.PostImplementation(IAlternateMethodContext context, TimerResult timerResult)
at Glimpse.Core.Extensibility.AlternateMethod.NewImplementation(IAlternateMethodContext context)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.IViewEngineProxy.FindView(ControllerContext controllerContext, String viewName, String masterName, Boolean useCache)
at System.Web.Mvc.ViewEngineCollection.<>c__DisplayClass6.<FindView>b__4(IViewEngine e)
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 lookup, Boolean trackSearchedPaths)
at System.Web.Mvc.ViewEngineCollection.Find(Func`2 cacheLocator, Func`2 locator)
at Postal.EmailViewRenderer.Render(Email email, String viewName)
at Postal.EmailService.Send(Email email)
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
at System.Web.Mvc.ActionMethodDispatcher.<>c__DisplayClass1.<WrapVoidAction>b__0(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__36(IAsyncResult asyncResult, ActionInvocation innerInvokeState)
at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult)
at Castle.Proxies.Invocations.AsyncControllerActionInvoker_EndInvokeActionMethod.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Glimpse.Mvc.AlternateType.AsyncActionInvoker.EndInvokeActionMethod.NewImplementation(IAlternateMethodContext context)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.AsyncControllerActionInvokerProxy.EndInvokeActionMethod(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3c()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass45.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3e()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass30.<BeginInvokeActionMethodWithFilters>b__2f(IAsyncResult asyncResult)
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass1e.<>c__DisplayClass28.<BeginInvokeAction>b__19()
at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass1e.<BeginInvokeAction>b__1b(IAsyncResult asyncResult)
I created a quick action to test it. The controller code is:
public void TestEmailExt()
{
var confirmationToken = "ConfirmationToken";
var Phone1 = "**********";
dynamic email = new Email("RegEmail");
email.To = "**#gmail.com";
email.UserName = "UserName";
email.ConfirmationToken = confirmationToken;
email.Phone = Extensions.Right(Phone1, 4);
if (email.To.Contains("#mydomain"))
email.From = INTERNAL_EMAIL_FROM;
else
email.From = EXTERNAL_EMAIL_FROM;
email.Send();
}
The reason this fails is because the Postal library creates its own HttpContext instance while rendering the email view as the decompiled CreateControllerContext method inside Postal's EmailViewRenderer class shows:
private ControllerContext CreateControllerContext()
{
HttpContextWrapper httpContextWrapper = new HttpContextWrapper(new HttpContext(new HttpRequest("", this.UrlRoot(), ""), new HttpResponse(TextWriter.Null)));
RouteData routeData = new RouteData();
routeData.Values["controller"] = (object) this.EmailViewDirectoryName;
return new ControllerContext(new RequestContext((HttpContextBase) httpContextWrapper, routeData), (ControllerBase) new EmailViewRenderer.StubController());
}
This means that the setup that Glimpse does at BeginRequest is completely removed, while the hooks are still in place to intercept MVC related calls.
We've had a similar issue where I gave a similar response to why this is not working.
UPDATE :
I mentioned above that a similar issue had been reported previously, but while I was trying to find a more appropriate solution, it seemed that this case is slightly different in that respect that the other similar issue actually executes a controller with the freshly created context resulting in a NullReferenceException in Glimpse specific code, while here we get a NullReferenceException inside MVC specific code, albeit triggered by Glimpse.
System.ArgumentNullException: Value cannot be null.
Parameter name: controllerContext
at System.Web.Mvc.ChildActionValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
And the exception we get here is because the ControllerContext property on the StubController instance (created inline) is null, which would normally be set when executing the controller (which is not the case here).
So the workaround that I proposed below still applies, but can be avoided if the code of the CreateControllerContext() above is slightly modified:
private ControllerContext CreateControllerContext()
{
HttpContextWrapper httpContextWrapper = new HttpContextWrapper(new HttpContext(new HttpRequest("", this.UrlRoot(), ""), new HttpResponse(TextWriter.Null)));
RouteData routeData = new RouteData();
routeData.Values["controller"] = (object) this.EmailViewDirectoryName;
// MODIFIED
var stubController = new EmailViewRenderer.StubController();
var controllerContext = new ControllerContext(new RequestContext(httpContextWrapper, routeData), stubController);
stubController.ControllerContext = controllerContext;
return controllerContext;
}
I've created an issue for this on the Postal issue tracker
END OF UPDATE
I think the best solution, for now, is to disable Glimpse while calling into Postal and restore normal Glimpse behavior back again afterwards. We might include this one way or the other into the Glimpse Core library in one of the upcoming releases as it seems that disabling Glimpse during a specific part of the request processing logic doesn't seem to be that uncommon, but for now the following snippet might help you (beware it makes use of a Glimpse internal key which is not guaranteed to be there in an upcoming release)
public class GlimpseSuppressionScope : IDisposable
{
private const string GlimpseRequestRuntimePermissionsKey = "__GlimpseRequestRuntimePermissions";
private readonly HttpContext currentHttpContext;
private readonly RuntimePolicy? currentRuntimePolicy;
private bool disposed;
public GlimpseSuppressionScope(HttpContext currentHttpContext)
{
if (currentHttpContext == null)
{
throw new ArgumentNullException("currentHttpContext");
}
this.currentHttpContext = currentHttpContext;
this.currentRuntimePolicy = this.currentHttpContext.Items[GlimpseRequestRuntimePermissionsKey] as RuntimePolicy?;
this.currentHttpContext.Items[GlimpseRequestRuntimePermissionsKey] = RuntimePolicy.Off;
}
~GlimpseSuppressionScope()
{
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.currentHttpContext != null)
{
this.currentHttpContext.Items.Remove(GlimpseRequestRuntimePermissionsKey);
if (this.currentRuntimePolicy.HasValue)
{
this.currentHttpContext.Items[GlimpseRequestRuntimePermissionsKey] = this.currentRuntimePolicy.Value;
}
}
}
this.disposed = true;
}
}
}
which you can then use in your controller action method as shown below:
public void TestEmailExt()
{
using (new GlimpseSuppressionScope(System.Web.HttpContext.Current))
{
var confirmationToken = "ConfirmationToken";
var Phone1 = "**********";
dynamic email = new Email("RegEmail");
email.To = "**#gmail.com";
email.UserName = "UserName";
email.ConfirmationToken = confirmationToken;
email.Phone = Extensions.Right(Phone1, 4);
if (email.To.Contains("#mydomain"))
email.From = INTERNAL_EMAIL_FROM;
else
email.From = EXTERNAL_EMAIL_FROM;
email.Send();
}
}

AppDomain.CurrentDomain.AssemblyResolve does not fire while loading plug-in for main software. Why?

The plug-in (let us call it PLUGIN) I am developing is using two assembly files from the main software (let as call it PARENT) it is written for. As soon as the PARENT is updated to a new version (and it happens several times a week) I want my PLUGIN to load the new versions of the dependencies dynamically without forcing me to re-compile.
The PARENT loads its plug-ins as source code files and compiles them just in time. Since I want my code to be in a DLL my Loader.cs file calls functions from my DLL via reflection.
The following is the code of the Loader.cs.
// error handling removed for better readability
public Loader()
{
assembly = Assembly.LoadFile(dllPath);
type = assembly.GetType("PLUGIN.PLUGINStarter");
instance = Activator.CreateInstance(type);
}
public override void Dispose()
{
type.InvokeMember("Dispose", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, instance, null);
base.Dispose();
}
public override void OnButtonPress()
{
type.InvokeMember("ShowForm", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, instance, null);
}
Now the PLUGINStarter class in PLUGIN namespace looks as follows.
class PLUGINStarter
{
private PLUGIN plugin = null;
/// <summary>
/// This is loading PARENT.exe and PARENTSomeOtherFile.dll dependencies.
/// </summary>
public PLUGINStarter()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, eventArgs) =>
{
var fullAssemblyName = new AssemblyName(eventArgs.Name);
// this is not executed when PARENT version changes
MessageBox.Show(fullAssemblyName.Name, "Loading assembly...");
if (fullAssemblyName.Name.Equals("PARENT"))
{
// AppDomain.CurrentDomain.FriendlyName will handle the case where PARENT.exe is re-named
var found = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, AppDomain.CurrentDomain.FriendlyName));
return found;
}
else if (fullAssemblyName.Name.Equals("PARENTSomeOtherFile"))
{
var found = Assembly.LoadFile(Path.Combine(Environment.CurrentDirectory, "PARENTSomeOtherFile.dll"));
return found;
}
else
{
return null;
}
};
Initialize();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Initialize()
{
// the PARENT's assemblies are referenced in the PLUGIN class
plugin = new PLUGIN();
}
public void ShowForm()
{
plugin.ShowForm();
}
public void Dispose()
{
plugin.Dispose();
}
}
When the PARENT is updated to a new version the event is not fired. Why?
EDIT #1
For clarification: PARENT loads (compiles it just in time) Loader.cs which loads the PLUGIN.dll which depends on assemblies from PARENT (and mainly PARENT.exe itself).
EDIT #2
The PARENT software is updated manually. The user downloads it from the internet (the web site of the product). Then the user copies my PLUGIN.dll into the "Plugins" directory of the PARENT.
Then I am able to catch the following exception in Loader.cs.
[07:55:19.822 D] [PLUGIN] Loading DLL 'C:\PNT\PARENTNew\Plugins\PLUGIN\PLUGIN.dll'.
[07:55:19.826 D] [PLUGIN] Exception loading assembly 'System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.IO.FileLoadException: Could not load file or assembly 'PARENT, Version=6.2.8113.191, Culture=neutral, PublicKeyToken=21a554ab5c01ae50' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
at PLUGIN.PLUGINStarter..ctor()
--- End of inner exception stack trace ---
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at PLUGINLoader.Loader..ctor() in c:\PNT\PARENTNew\Plugins\PLUGIN\Loader.cs:line 42'.
EDIT #3
I believe this is possible as described in Is it possible to replace a reference to a strongly-named assembly with a "weak" reference? but for some strange reason it fails in my application.
EDIT #4
I solved the problem by removing PLUGINStarter and moving the assembly resolving code to the constructor of Loader.cs. Now everything resolves nicely despite wrong assembly versions.
It's getting to long for a comment, so I place it as an answer.
I still don't understand why do you load a PARENT assembly that is already loaded or how do you "update" PARENT...
If a dll is loaded there is generally no way to unload it. You must create a new AppDomain to make it work.
So as I see it, you run your PARENT, it loads the PLUGIN, then the event is attached.
According to http://msdn.microsoft.com/en-us//library/ff527268.aspx
When you register a handler for the AssemblyResolve event, the handler is invoked whenever the runtime fails to bind to an assembly by name. For example, calling the following methods from user code can cause the AssemblyResolve event to be raised:
An AppDomain.Load method overload or Assembly.Load method overload whose first argument is a string that represents the display name of the assembly to load (that is, the string returned by the Assembly.FullName property).
An AppDomain.Load method overload or Assembly.Load method overload whose first argument is an AssemblyName object that identifies the assembly to load.
An Assembly.LoadWithPartialName method overload.
An AppDomain.CreateInstance or AppDomain.CreateInstanceAndUnwrap method overload that instantiates an object in another application domain.
That means you have to explicitly load an assembly (and fail to do it automatically) to fire the event.
So I believe the "updating" of PARENT is crucial here. How do you do it?
EDIT
According to your edits, I believe your answer can be found here: If I rebuild a dll that my project references, do I have to rebuild the project also?
This link also mentions something about strict forcing of referenced assembly's version and how to avoid it.
As described in EDIT #4 the assembly event was not registered soon enough.
I solved the problem by removing PLUGINStarter and moving the assembly resolving code to the constructor of Loader.cs. Now everything resolves nicely despite wrong assembly versions.

Adding a button in UICollectionViewCell with a click handler using Xamarin.iOS

I am rewriting a screen that used a UITableView before to UICollectionView. But I am having trouble with the click handler on a button inside the cell.
collectionView.RegisterNibForCell (DocumentViewCell.Nib, docCellId);
...
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (docCellId, indexPath);
docCell.BtnDelete.Hidden = !EditMode;
docCell.BtnDelete.TouchUpInside += delegate(object sender, EventArgs e) {
Logging.Debug("Crashes before if reaches here");
};
return docCell;
}
I am aware that the cell is begin reused and this most likely won't work when that happens but now it crashes immediately with just one element in the list when pressing the delete button. Everything works great until I hit the button and then I get the stacktrace below. I see no reason for that happening based on my UITableView code that is almost identical.
Have anyone done this using Nib based CollectionView cells? Any help is very appreciated!
Stacktrace:
at (wrapper managed-to-native) MonoTouch.UIKit.UIApplication.UIApplicationMain (int,string[],intptr,intptr) <IL 0x0009f, 0xffffffff>
at MonoTouch.UIKit.UIApplication.Main (string[],string,string) [0x0004c] in /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:38
at SalesApp.Application.Main (string[]) [0x00000] in /MyApp/Main.cs:18
at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object (object,intptr,intptr,intptr) <IL 0x00050, 0xffffffff>
Native stacktrace:
0 SalesApp 0x0009a85c mono_handle_native_sigsegv + 284
1 SalesApp 0x0000e138 mono_sigsegv_signal_handler + 248
2 libsystem_c.dylib 0x990a78cb _sigtramp + 43
3 ??? 0xffffffff 0x0 + 4294967295
4 UIKit 0x01990258 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 61
5 UIKit 0x01a51021 -[UIControl sendAction:to:forEvent:] + 66
6 UIKit 0x01a5157f -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 578
7 UIKit 0x01a506e8 -[UIControl touchesEnded:withEvent:] + 546
8 UIKit 0x01c541d3 _UIGestureRecognizerUpdate + 7407
9 CoreFoundation 0x03ecbafe __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
Update
This code works fine
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (DocumentViewCell.Key, indexPath);
docCell.BtnDelete.Hidden = !EditMode;
docCell.BtnDelete.TouchUpInside -= HandleTouchUpInside;
docCell.BtnDelete.TouchUpInside += HandleTouchUpInside;
return docCell;
}
void HandleTouchUpInside (object sender, EventArgs e)
{
Logging.Debug("No crash");
}
The issue is that there's no managed reference to the docCell instance you return from GetCell method. That means the GC can collect it whenever it wants.
When it's needed again it will be resurfaced to the managed world (using the IntPtr constructor). It's gonna be the same (re-used) native instance but a new managed instance. Your crash occurs because the event points to the old, managed, instance (that was collected).
The easy solution is to keep a cache of the created cells as along as the view exists. That will ensure the GC won't collect the (managed) cells before they are used. There's several answers showing this for UITableView.
Note: I thought we fixed (hided) this in recent version of Xamarin.iOS. Maybe it was just for UITableView !?! need to check on this...
I guess DequeueReusableCell returns null as it's the first cell being drawn.
Your code should look like this instead:
public override UICollectionViewCell GetCell (UICollectionView collectionView, MonoTouch.Foundation.NSIndexPath indexPath)
{
var docCell = (DocumentViewCell)collectionView.DequeueReusableCell (docCellId, indexPath);
if (docCell == null)
docCell = new UICollectionViewCell (...);
docCell.BtnDelete.TouchUpInside += delegate(object sender, EventArgs e) {
Logging.Debug("Crashes before if reaches here");
};
return docCell;
}
replace the ellipses (...) by whatever creates your cell.

Resources