Xamarin.GooglePlayServices.Ads: how to add a bundle to the ad request - c#

Considering I have an AdView in a Xamarin.Android project:
private AdView _bannerAd;
I usually initialize it like this:
_bannerAd = new AdView(this)
{
AdSize = AdSize.SmartBanner,
AdUnitId = adUnitId,
Visibility = ViewStates.Visible
};
Then, when I load the banner, I have to build the request. In this case I'd like to add an extra bundle, but when I have to call requestbuilder.AddCustomEventExtrasBundle, I don't know what to put as the first parameter.
var requestbuilder = new AdRequest.Builder();
var extras = new Bundle();
extras.PutString("npa", "1");
requestbuilder.AddCustomEventExtrasBundle(***, extras);
_bannerAd.LoadAd(requestbuilder.Build())
By reading the method definition, I really don't understand what could be an "adapter class".
[Register("addCustomEventExtrasBundle", "(Ljava/lang/Class;Landroid/os/Bundle;)Lcom/google/android/gms/ads/AdRequest$Builder;", "")]
public Builder AddCustomEventExtrasBundle(Class adapterClass, Bundle customEventExtras);

You need to pass the Java Class (via Java.Lang.Class.FromType) of your custom event (ICustomEventBanner).
In my case, I have one called SushiHangoverTextEventBanner that is registered with AdMob.
You need to implement ICustomEventBanner, assumably this is a stand alone object (in my case it is) as AdMob will instance it, inherit it from Java.Lang.Object so Xamarin will create the ACW (JNI/Java wrapper) for it.
public class SushiHangoverTextEventBanner : Java.Lang.Object, ICustomEventBanner
{
SushiHangoverTextAdView customAdView;
public void OnDestroy()
{
customAdView?.Dispose();
}
public void OnPause()
{
~~~
}
public void OnResume()
{
~~~
}
public void RequestBannerAd(Context context, ICustomEventBannerListener listener, string serverParameter, AdSize size, IMediationAdRequest mediationAdRequest, Bundle customEventExtras)
{
customAdView = new SushiHangoverTextAdView(context);
~~~
}
}
I also have a custom ad (SushiHangoverAdView based on a TextView) that is used within that ICustomEventBanner implementation.
Once you register it and implement the AdMob callbacks, you can just pass it to your AdRequest.Builder:
using (var requestbuilder = new AdRequest.Builder())
{
var extras = new Bundle();
extras.PutString("npa", "1");
requestbuilder.AddCustomEventExtrasBundle(Java.Lang.Class.FromType(typeof(SushiHangoverTextEventBanner)), extras);
}
I help recommend going through the Admob custom event example:
https://developers.google.com/admob/android/custom-events

It is expecting a class that extends from CustomEvent, per the Documentation
public AdRequest.Builder addCustomEventExtrasBundle (Class<? extends
CustomEvent> adapterClass, Bundle customEventExtras)
Here is a great tutorial on getting started with custom events, directly from Google, where they go over using the CustomEventBanner. It is in Java, but should be easy enough to port to C#

Related

How to setup NLog with Sustainsys.Saml2

I have an ASP.Net Web Forms app where I just integrated Sustainsys.Saml2 library.
I've never used any sort of logging mechanism and I'm trying to figure out how to add or create an ILoggerAdapter for the library stated on their troubleshooting page.
I've decided to use NLog (please feel free to recommend a different one) and either I'm not understanding this well, or am not using the right keyword to search for what I need/want, or their isn't a lot of documentation on it.
Currently, I'm using the HttpModule version of Sustainsys.Saml2. Any other information available upon request.
Any help would be great.
Currently, I'm configuring the Sustainsys.Saml2 library through both web.config and the global.asax files. Here's the class my global.asax calls:
public class Saml2Config {
private static bool _alreadyInitialized;
private static readonly object Lock = new object();
public static void Initialize() {
if (_alreadyInitialized) {
return;
}
lock (Lock) {
if (_alreadyInitialized) {
return;
}
var domain = PageHelper.GetDomainURL(true);
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.EntityId.Id = $"{domain}/federation/Saml2";
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.ModulePath = "/federation/Saml2";
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.ReturnUrl = new Uri($"{domain}/mybarry");
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.PublicOrigin = new Uri($"{domain}");
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.Logger = new NullLoggerAdapter();
_alreadyInitialized = true;
}
}
}
The interface is pretty straightforward
public interface ILoggerAdapter
{
void WriteInformation(string message);
void WriteError(string message, Exception ex);
void WriteVerbose(string message);
}
I would implement it as follows:
public class NLogAdapter : ILoggerAdapter
{
private static Logger Logger = LogManager.GetLogger("Saml2");
public void WriteInformation(string message)
{
Logger.Info(message);
}
public void WriteError(string message, Exception ex)
{
Logger.Error(ex, message);
}
public void WriteVerbose(string message)
{
Logger.Debug(message);
}
}
And finally set it:
Sustainsys.Saml2.Configuration.Options.FromConfiguration.SPOptions.Logger = new NLogAdapter();
The ILoggerAdapter contains methods for different loglevels. Make an adapter class that implements ILoggerAdapter and writes to NLog. Then set SPOptions.Logger to an instance of your adapter class.
If you want an example, you can check out the adapter for Asp.Net Core that logs to the Asp.Net Core logging system and is the default for the Sustainsys.Saml2.AspNetCore2 package: https://github.com/Sustainsys/Saml2/blob/master/Sustainsys.Saml2.AspNetCore2/AspNetCoreLoggerAdapter.cs
For the Sustainsys.Saml2.HttpModule library the default is the NullLoggerAdapter which simply discards any logs. Only reason to use it is to not have to nullcheck the Logger property everywhere it is used (that code was written before the ?. syntax was introduced.)

How do I spy on a method from a sealed library class? [duplicate]

I have an MVC web app, and I'm using Simple Injector for DI. Almost all my code is covered by unit tests. However, now that I've added some telemetry calls in some controllers, I'm having trouble setting up the dependencies.
The telemetry calls are for sending metrics to the Microsoft Azure-hosted Application Insights service. The app is not running in Azure, just a server with ISS. The AI portal tells you all kinds of things about your application, including any custom events you send using the telemetry library. As a result, the controller requires an instance of Microsoft.ApplicationInsights.TelemetryClient, which has no Interface and is a sealed class, with 2 constructors. I tried registering it like so (the hybrid lifestyle is unrelated to this question, I just included it for completeness):
// hybrid lifestyle that gives precedence to web api request scope
var requestOrTransientLifestyle = Lifestyle.CreateHybrid(
() => HttpContext.Current != null,
new WebRequestLifestyle(),
Lifestyle.Transient);
container.Register<TelemetryClient>(requestOrTransientLifestyle);
The problem is that since TelemetryClient has 2 constructors, SI complains and fails validation. I found a post showing how to override the container's constructor resolution behavior, but that seems pretty complicated. First I wanted to back up and ask this question:
If I don't make the TelemetryClient an injected dependency (just create a New one in the class), will that telemetry get sent to Azure on every run of the unit test, creating lots of false data? Or is Application Insights smart enough to know it is running in a unit test, and not send the data?
Any "Insights" into this issue would be much appreciated!
Thanks
Application Insights has an example of unit testing the TelemetryClient by mocking TelemetryChannel.
TelemetryChannel implements ITelemetryChannel so is pretty easy to mock and inject. In this example you can log messages, and then collect them later from Items for assertions.
public class MockTelemetryChannel : ITelemetryChannel
{
public IList<ITelemetry> Items
{
get;
private set;
}
...
public void Send(ITelemetry item)
{
Items.Add(item);
}
}
...
MockTelemetryChannel = new MockTelemetryChannel();
TelemetryConfiguration configuration = new TelemetryConfiguration
{
TelemetryChannel = MockTelemetryChannel,
InstrumentationKey = Guid.NewGuid().ToString()
};
configuration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
TelemetryClient telemetryClient = new TelemetryClient(configuration);
container.Register<TelemetryClient>(telemetryClient);
Microsoft.ApplicationInsights.TelemetryClient, which has no Interface and is a sealed class, with 2 constructors.
This TelemetryClient is a framework type and framework types should not be auto-wired by your container.
I found a post showing how to override the container's constructor resolution behavior, but that seems pretty complicated.
Yep, this complexity is deliberate, because we want to discourage people from creating components with multiple constructors, because this is an anti-pattern.
Instead of using auto-wiring, you can, as #qujck already pointed out, simply make the following registration:
container.Register<TelemetryClient>(() =>
new TelemetryClient(/*whatever values you need*/),
requestOrTransientLifestyle);
Or is Application Insights smart enough to know it is running in a unit test, and not send the data?
Very unlikely. If you want to test the class that depends on this TelemetryClient, you better use a fake implementation instead, to prevent your unit test to either become fragile, slow, or to pollute your Insight data. But even if testing isn't a concern, according to the Dependency Inversion Principle you should depend on (1) abstractions that are (2) defined by your own application. You fail both points when using the TelemetryClient.
What you should do instead is define one (or perhaps even multiple) abstractions over the TelemetryClient that are especially tailored for your application. So don't try to mimic the TelemetryClient's API with its possible 100 methods, but only define methods on the interface that your controller actually uses, and make them as simple as possible so you can make both the controller's code simpler -and- your unit tests simpler.
After you defined a good abstraction, you can create an adapter implementation that uses the TelemetryClient internally. I image you register this adapter as follows:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(new TelemetryClient(...)));
Here I assume that the TelemetryClient is thread-safe and can work as a singleton. Otherwise, you can do something like this:
container.RegisterSingleton<ITelemetryLogger>(
new TelemetryClientAdapter(() => new TelemetryClient(...)));
Here the adapter is still a singleton, but is provided with a delegate that allows creation of the TelemetryClient. Another option is to let the adapter create (and perhaps dispose) the TelemetryClient internally. That would perhaps make the registration even simpler:
container.RegisterSingleton<ITelemetryLogger>(new TelemetryClientAdapter());
I had a lot of success with using Josh Rostad's article for writing my mock TelemetryChannel and injecting it into my tests. Here's the mock object:
public class MockTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries = new ConcurrentBag<ITelemetry>();
public bool IsFlushed { get; private set; }
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; }
public void Send(ITelemetry item)
{
this.SentTelemtries.Add(item);
}
public void Flush()
{
this.IsFlushed = true;
}
public void Dispose()
{
}
}
And then in my tests, a local method to spin-up the mock:
private TelemetryClient InitializeMockTelemetryChannel()
{
// Application Insights TelemetryClient doesn't have an interface (and is sealed)
// Spin -up our own homebrew mock object
MockTelemetryChannel mockTelemetryChannel = new MockTelemetryChannel();
TelemetryConfiguration mockTelemetryConfig = new TelemetryConfiguration
{
TelemetryChannel = mockTelemetryChannel,
InstrumentationKey = Guid.NewGuid().ToString(),
};
TelemetryClient mockTelemetryClient = new TelemetryClient(mockTelemetryConfig);
return mockTelemetryClient;
}
Finally, run the tests!
[TestMethod]
public void TestWidgetDoSomething()
{
//arrange
TelemetryClient mockTelemetryClient = this.InitializeMockTelemetryChannel();
MyWidget widget = new MyWidget(mockTelemetryClient);
//act
var result = widget.DoSomething();
//assert
Assert.IsTrue(result != null);
Assert.IsTrue(result.IsSuccess);
}
If you don't want to go down the abstraction / wrapper path. In your tests you could simply direct the AppInsights endpoint to a mock lightweight http server (which is trivial in ASP.NET Core).
appInsightsSettings.json
"ApplicationInsights": {
"Endpoint": "http://localhost:8888/v2/track"
}
How to set up "TestServer" in ASP.NET Core http://josephwoodward.co.uk/2016/07/integration-testing-asp-net-core-middleware
Another option without going the abstraction route is to disable telemetry before doing running your tests:
TelemetryConfiguration.Active.DisableTelemetry = true;
Based on other work here;
Create the channel - you can use this for testing telemetries if needed
public class MockTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries = new();
public bool IsFlushed { get; private set; }
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; }
public void Send(ITelemetry item)
{
this.SentTelemtries.Add(item);
}
public void Flush()
{
this.IsFlushed = true;
}
public void Dispose()
{
}
}
Use a nice little static factory class
public static class MockTelemetryClient
{
public static TelemetryClient Create()
{
var mockTelemetryChannel = new MockTelemetryChannel();
var mockTelemetryConfig = new TelemetryConfiguration
{
TelemetryChannel = mockTelemetryChannel,
InstrumentationKey = Guid.NewGuid().ToString()
};
var mockTelemetryClient = new TelemetryClient(mockTelemetryConfig);
return mockTelemetryClient;
}
}
Call MockTelemetryClient.Create() to get your TelemetryClient
Profit
A colleague of mine wrote this useful library that introduces abstractions for some of these core telemetry types (e.g. ITelemetryClient and IMetric).
https://github.com/thomhurst/ApplicationInsights.TelemetryLogger
Very easy to implement. You'll barely have to change anything in your production code, and mocking in tests becomes a breeze. Here's an extract from the README:
Dependency Injection
Call AddApplicationInsightsTelemetry() as normal, and then call AddApplicationInsightsTelemetryClientInterfaces()
public void ConfigureServices(IServiceCollection services)
{
services
.AddApplicationInsightsTelemetry()
.AddApplicationInsightsTelemetryClientInterfaces();
}
ITelemetryClient
Want the same usage as TelemetryClient? Inject ITelemetryClient into your classes. It has all the available methods of TelemetryClient (apart from any methods which shouldn't be called. e.g. internal or deprecated).
public class MyClass
{
private readonly ITelemetryClient _telemetryClient;
public MyClass(ITelemetryClient telemetryClient)
{
_telemetryClient = telemetryClient;
}
public void DoSomething()
{
_telemetryClient.TrackTrace("Something happened");
}
}

Extension method to chain type registrations in Autofac as options

I am trying to implement some syntactic sugar around the registration of multiple things with Autofac but I'm stuck and I don't know whether this has some design pattern name where I could search some information for.
I have something like this in my main project:
protected override void Load(ContainerBuilder builder)
{
var busBuilder =
new BusBuilder()
.RegisterHandler<EventOne, DomainEventHandlerAdapter<EventOne>>()
.RegisterHandler<EventTwo, DomainEventHandlerAdapter<EventTwo>>()
.RegisterHandler<EventThree, DomainEventHandlerAdapter<EventThree>>();
builder
.RegisterMicroBus(busBuilder);
builder
.RegisterType<MicroBusDomainEventBus>()
.As<IDomainEventBus>();
}
I want to refactor this, because I don't want my main project to have a dependency to that specific NuGet package (the Enexure Microbus package). So I would like to have in my main project something like this:
protected override void Load(ContainerBuilder builder)
{
builder.RegisterMyMicrobus(eventOptions => {
eventOptions
.RegisterDomainHandler<EventOne>()
.RegisterDomainHandler<EventTwo>()
.RegisterDomainHandler<EventThree>();
});
}
so that my main project knows nothing about BusBuilder, DomainEventHandlerAdapter or RegisterMicroBus and has no dependency on that specific technology (Enexure Microbus). All the Autofac registration would be handled by my other project that is the only one with a dependency to the specific technology.
Here is what I have done so far on my other project:
I have created an extension method on Autofac's ContainerBuilder where I will pass the eventOptions, which would be a delegate (lambda expression) Action<EventOption>
public static void RegisterMyMicrobus(this ContainerBuilder builder, Action<EventOption> eventOptions = null)
{
var busBuilder = new BusBuilder();
if (eventOptions != null)
{
// TODO, how to implement EventOption and the chain registration?
}
builder
.RegisterMicroBus(busBuilder);
builder
.RegisterType<MicroBusDomainEventBus>()
.As<IDomainEventBus>();
}
Any link or help would be greatly appreciated. This pattern must have a name, but no idea how is it called and I don't have experience implementing chain methods.
UPDATE 1: Following the advice of Erndob I had a look at BusBuilder. But again I reached a point where I am stuck.
I have created EventOption as follows:
public class EventOption
{
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
private readonly List<IDomainEvent> _domainEvents= new List<IDomainEvent>();
public EventOption RegisterDomainHandler(IDomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
return this;
}
}
but now the problem is with the extension method, because I can retrieve the list of events
public static void RegisterMyMicrobus(this ContainerBuilder builder, Action<EventOption> eventOptions = null)
{
var busBuilder = new BusBuilder();
// use busBuilder to register each one of the domain events in options
var eventOption = new EventOption();
eventOptions?.Invoke(eventOption);
foreach (var domainEvent in eventOption.DomainEvents)
{
var eventType = domainEvent.GetType();
// HERE is the problem. How to RegisterHandler using this generic method?
busBuilder
.RegisterHandler<eventType, DomainEventHandlerAdapter<eventType>>();
}
builder
.RegisterMicroBus(busBuilder);
builder
.RegisterType<MicroBusDomainEventBus>()
.As<IDomainEventBus>();
}
It won't compile because I am trying to use a variable as a Type (RegisterHandler is a generic). I can't find a way to convert my variable into a type to satisfy that call. Any idea or a different way to achieve the same? Thanks again
UPDATE 2: I have changed the approach and it seems it could work. It compiles and it registers everything, but I have to test further.
I have modified EventOption to store Types:
public class EventOption
{
public IReadOnlyList<Type> Types => _types.AsReadOnly();
private readonly List<Type> _types = new List<Type>();
public DomainCommandOptions RegisterCommandHandler<TDomainCommand>()
where TDomainCommand : IDomainCommand
{
var domainCommandType = typeof(TDomainCommand);
_types.Add(domainCommandType);
return this;
}
}
and then the extension method is now different, because it iterates over the types and at runtime it creates a generic type. I discovered there is a HandlerRegistration from Microbus I can use directly.
public static void RegisterMyMicrobus(this ContainerBuilder builder, Action<EventOption> eventOptions = null)
{
var busBuilder = new BusBuilder();
// use busBuilder to register each one of the domain events in options
var eventOption = new EventOption();
eventOptions?.Invoke(eventOption);
foreach (var type in eventOption.Types)
{
var handlerRegistration = new HandlerRegistration(type, typeof(DomainEventHandlerAdapter<>).MakeGenericType(type));
busBuilder.RegisterMessage(handlerRegistration);
}
builder
.RegisterMicroBus(busBuilder);
builder
.RegisterType<MicroBusDomainEventBus>()
.As<IDomainEventBus>();
}
Have a look at the source code of the project you are using. The BusBuilder, the thing that chains, has a pattern name in it's name. A builder. The repo itself says:
MicroBus is a simple in process mediator for .NET
So MicroBus is a Mediator. Have a look at a mediator pattern.
Here's how the BusBuilder.cs looks: https://github.com/Lavinski/Enexure.MicroBus/blob/master/src/Enexure.MicroBus/BusBuilder.cs
The building itself is essentially done like this:
public BusBuilder RegisterHandler<TMessage, TMessageHandler>()
where TMessageHandler : IMessageHandler<TMessage, Unit>
{
registrations.Add(HandlerRegistration.New<TMessage, TMessageHandler>());
return this;
}
As you can see, the instance has a list of registrations, then when you want to register something new, it just adds to the list and then returns itself, letting you to chain a new registration.

Write device platform specific code in Xamarin.Forms

I have the following Xamarin.Forms.ContentPage class structure
public class MyPage : ContentPage
{
public MyPage()
{
//do work to initialize MyPage
}
public void LogIn(object sender, EventArgs eventArgs)
{
bool isAuthenticated = false;
string accessToken = string.Empty;
//do work to use authentication API to validate users
if(isAuthenticated)
{
//I would to write device specific code to write to the access token to the device
//Example of saving the access token to iOS device
NSUserDefaults.StandardUserDefaults.SetString(accessToken, "AccessToken");
//Example of saving the access token to Android device
var prefs = Application.Context.GetSharedPreferences("MySharedPrefs", FileCreationMode.Private);
var prefsEditor = prefs.Edit();
prefEditor.PutString("AccessToken", accessToken);
prefEditor.Commit();
}
}
}
I would like to write platform specific code in the MyPage LogIn method to save the access token based on which device OS they are using my application on.
How do I only run device specific code when the user uses my application on their device?
This is a scenario which is easily resolved with dependency injection.
Have a interface with the desired methods on your shared or PCL code, like:
public interface IUserPreferences
{
void SetString(string key, string value);
string GetString(string key);
}
Have a property on your App class of that interface:
public class App
{
public static IUserPreferences UserPreferences { get; private set; }
public static void Init(IUserPreferences userPreferencesImpl)
{
App.UserPreferences = userPreferencesImpl;
}
(...)
}
Create platform-specific implementations on your target projects:
iOS:
public class iOSUserPreferences : IUserPreferences
{
public void SetString(string key, string value)
{
NSUserDefaults.StandardUserDefaults.SetString(value, key);
}
public string GetString(string key)
{
return NSUserDefaults.StandardUserDefaults.StringForKey(key);
}
}
Android:
public class AndroidUserPreferences : IUserPreferences
{
public void SetString(string key, string value)
{
var prefs = Application.Context.GetSharedPreferences("MySharedPrefs", FileCreationMode.Private);
var prefsEditor = prefs.Edit();
prefEditor.PutString(key, value);
prefEditor.Commit();
}
public string GetString(string key)
{
(...)
}
}
Then on each platform-specific project create an implementation of IUserPreferences and set it using either App.Init(new iOSUserPrefernces()) and App.Init(new AndroidUserPrefernces()) methods.
Finally, you could change your code to:
public class MyPage : ContentPage
{
public MyPage()
{
//do work to initialize MyPage
}
public void LogIn(object sender, EventArgs eventArgs)
{
bool isAuthenticated = false;
string accessToken = string.Empty;
//do work to use authentication API to validate users
if(isAuthenticated)
{
App.UserPreferences.SetString("AccessToken", accessToken);
}
}
}
Xamarin.Forms 2.3.4 introduced a new method for this:
if (Device.RuntimePlatform == Device.Android)
{
// Android specific code
}
else if (Device.RuntimePlatform == Device.iOS)
{
// iOS specific code
}
else if (Device.RuntimePlatform == Device.UWP)
{
// UWP specific code
}
There are also other platforms to choose from, you can type in Device. in Visual Studio and it will show you the options.
There are multiple answers, depending on what you want to achieve, and the kind of project you have:
Execute different Xamarin.Forms code on different platforms.
Use this e.g. if you want different font sizes on different platforms:
label.Font = Device.OnPlatform<int> (12, 14, 14);
Execute platform specific code in a shared (PCL) project
The common pattern is to use DI (dependency injection) for this. Xamarin.Forms provides a simple DependencyService for this, but use whatever you want.
Execute platform specific code in shared (Shared Asset Project) project
As the code is compiled per platform, you can wrap your platform specific code in #if __PLATFORM__ #endif and have all the code in the same file. The platform project should define __IOS__, __ANDROID__ and __WINDOWS_PHONE__. Note that a shared asset project containing Xaml and code won't work well for iOS on Xamarin.Studio, and that having compiler directives makes your code harder to read and to test.
Xamarin.Forms has a built-in dependency injector if you take a look at their guide in the developer area of their website (http://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/)
There's also a wonderful library you can pull from NuGet/Github (https://github.com/aritchie/acr-xamarin-forms) that will handle the storage requirement you are looking for... take a look at the Settings service in there, it will even handle serialization of more complex objects.
This seems less about Xamarin.Forms and more about using defaults in a PCL. Check out James Montemagno's github repo for doing cross-platform defaults.
Then just call his static methods for setting/retrieving. The nuget package is Xam.Plugins.Settings.
It can be used like this:
using Refractored.Xam.Settings;
...
CrossSettings.Current.AddOrUpdateValue("AccessToken", accessToken);
var value = CrossSettings.Current.GetValueOrDefault<string>("AccessToken");

MVP Navigation in WinForms

Have been learning about MVP and have tried writing a test app using it in WinForms. I'm struggling to find a well explained example on how to navigate between my forms/views. As an example, the program starts and I want to show a login dialog then go into my main view if the login was successful. At the moment, my Main method looks something like this:
static void Main()
{
var loginView = Injector.Resolve<ILoginView>();
if (loginView.DoLogin() != LoginResult.OK) return;
var mainView = Injector.Resolve<IMainView>();
Application.Run(mainView); // won't work as mainView isn't a form
}
The Injector object is just a wrapper around an IoC tool (currently StructureMap). The thing is, I've read that I shouldn't really be manually creating instances via the Injector as they should really be done via constructor injection.
I've managed to do this up to a point but not when it comes to navigation. I can't think of an elegant way of moving through my views and was wondering if anyone here might shed some light on this? I've read a little on application controllers but have not found an example to show it clearly.
In regards to the navigation question:
I've managed to do this up to a point but not when it comes to
navigation. I can't think of an elegant way of moving through my views
and was wondering if anyone here might shed some light on this? I've
read a little on application controllers but have not found an example
to show it clearly.
Below is a simplified version of a construct I've used. Note that the setup and tear down hooks are called automatically when the NavigateTo method is called. Also, +1 to #AlexBurtsev, as his answer hints at this very same approach.
// Presenter can and should offer common services for the
// subclasses
abstract class Presenter
{
public void Display()
{
OnDisplay();
}
public void Dismiss()
{
OnDismiss();
}
protected virtual OnDisplay() // hook for subclass
{
}
protected virtual OnDismiss() // hook for subclass
{
}
private NavigationManager _navMgr;
internal NavigationMgr NavigationManager
{
get
{
return _navMgr;
}
set
{
_navMgr = value;
}
}
}
// NavigationManager is used to transition (or navigate)
// between views
class NavigationManager
{
Presenter _current;
// use this override if your Presenter are non-persistent (transient)
public void NavigateTo(Type nextPresenterType, object args)
{
Presenter nextPresenter = Activator.CreateInstance(nextPresenterType);
NavigateTo(nextPresenter);
}
// use this override if your Presenter are persistent (long-lived)
public void NavigateTo(Presenter nextPresenter, object args)
{
if (_current != null)
{
_current.Dismiss()
_current.NavigationMgr = null;
_current = null;
}
if (nextPresenter != null)
{
_current = nextPresenter;
_current.NavigationMgr = this;
_current.Display(args);
}
}
}
class MainMenuPresenter : Presenter
{
private IMainMenuView _mainMenuView = null;
// OnDisplay is your startup hook
protected override void OnDisplay()
{
// get your view from where ever (injection, etc)
_mainMenuView = GetView();
// configure your view
_mainMenuView.Title = GetMainTitleInCurrentLanguage();
// etc
// etc
// listen for relevent events from the view
_mainMenuView.NewWorkOrderSelected += new EventHandler(MainMenuView_NewWorkOrderSelected);
// display to the user
_mainMenuView.Show();
}
protected override void OnDismiss()
{
// cleanup
_mainMenuView.NewWorkOrderSelected -= new EventHandler(MainMenuView_NewWorkOrderSelected);
_mainMenuView.Close();
_mainMenuView = null;
}
// respond to the various view events
private void MainMenuView_NewWorkOrderSelected(object src, EventArgs e)
{
// example of transitioning to a new view here...
NavigationMgr.NavigateTo(NewWorkOrderPresenter, null);
}
}
class NewWorkOrderPresenter : Presenter
{
protected override void OnDisplay()
{
// get the view, configure it, listen for its events, and show it
}
protected override void OnDismiss()
{
// unlisten for events and release the view
}
}
I haven't used WinForms for a long time, but I'll try to answer this. I would use the same strategy as WPF Prism do.
About MainView and Application.Run:
Create a main Region (root Form), with empty container inside which can hold UserControl (I forgot exact class names), then when you need to switch root view, you do RootView.SetView(UserControl view) which will do something like Form.Clear(), Form.AddChild(view).
About the navigation and using container:
You could create a service for navigation: INavigationService which you inject in constructors with method like INavigationService.NavigateView(String(or Type) viewName, params object[] additionalData)
You can insert a method in mainView that returns the actual form.Then you can call
Mainview:IMainView
{
Form GetView()
{
//return new Form();
}
}
In Main you can call ,
Application.Run(mainView.GetView())

Categories

Resources