I have a little problem, could you explain me what is the best practice to load menu items from DB using MVC5 and Entity Framework6. The menu and localization object must be loaded only once, and then just used from some globally available collection. They are not going to change alot after website launch, so I just goung to implement some Update() method and I'll call it when necessary...
Use child actions.
public class FooController : Controller
{
...
[ChildActionOnly]
public ActionResult SiteMenu()
{
// load menu items however you need to
return PartialView("_SiteMenu", menuModel);
}
}
/Views/Shared/_SiteMenu.cshtml
#model Namespace.To.MenuModel
<!-- render your menu here -->
/Views/Shared/_Layout.cshtml
<!-- wherever you want the menu to display -->
#Html.Action("SiteMenu", "Foo")
If you want to cache the result, so the menu doesn't have to be pulled from the DB each request, then you can use the OutputCache attribute on the child action like any other action.
As i have already sad, I have thinked about Global.asax
So there is currently 2 ways how I can do it with Global.asax:
Update using this method is bad idea, use the second one instead
public static ICollection<MenuItem> MenuItems {
get
{
if (Application["MenuItems"] != null)
return (ICollection<MenuItems>)Application["MenuItems"];
else
return new ICollection<MenuItems>();
}
set
{
Application["MenuItems"] = value;
}
}
private void LoadMenuItems()
{
MyContext mc = new MyContext();
this.MenuItems = ms.MenuItems.Include("SubCategories").AsNotTacking().where(x => x.SubCategory == null).ToArray();
}
protected void Application_Start(object sender, EventArgs e)
{
this.MenuItems = LoadMenuItems();
}
And another way (The second one):
public static ICollection<MenuItem> MenuItems { get; set; }
private void LoadMenuItems()
{
MyContext mc = new MyContext();
this.MenuItems = ms.MenuItems.Include("SubCategories").AsNotTacking().where(x => x.SubCategory == null).ToArray();
}
protected void Application_Start(object sender, EventArgs e)
{
this.MenuItems = LoadMenuItems();
}
And same thing for Localization...
Actually i dont know which one is better, need to run some tests.
Almost forgot:
All the things, are contained in the "CustomHttpApplication" class, which is derrived from "HttpApplication" class. and Global.asax shoul be derived from "CustomHttpApplication" class. This way the Global.asax file will be cean and readable, but the business logic will be located one level down...
So the complete code could look like so:
CustomHttpApplication.cs
public class CustomHttpApplication : HttpApplication
{
public static ICollection<MenuItem> MenuItems { get; set; }
private void LoadMenuItems()
{
MyContext mc = new MyContext();
this.MenuItems = ms.MenuItems.Include("SubCategories").AsNotTacking().where(x => x.SubCategory == null).ToArray();
}
}
Global.asax.cs
public class MvcApplication : CustomHttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
MenuItems = this.LoadMenuItems();
}
}
And one more edit, if you/me convert the "LoadMenuItems" method to be a "static" one, than you/me will be able to update MenuItems and/or Localization item collections when needed.
Related
I am trying to call this GetProductStatus() method on a page button click event, but it's loading before the button click. Means when the ViewModel is loading, this is also load automatically.
I would like to declared this VM method "GetProductStatus()" to be called only when a button click event occurs.
ViewModel method:
private async void GetProductStatus()
{
try
{
IsBusy = true;
var status = await ProductStatusService.GetProductStatus(new ProductStatus()
{
StoreCode = s_code,
StartTime = StartDateValue.AddMinutes(time1),
EndTime = StartDateValue.AddMinutes(time2)
});
IsBusy = false;
if (status != null)
{
//Process happens
}
else
{
//Array is Null
}
ProductStatus = status;
}
catch (Exception)
{
ProductStatus = null;
}
}
Here, the method is declared.
public ProductViewModel(INavigation nav, Store store)
{
_Nav = nav;
GetProductStatus();
}
Here, the clicked event.
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
I would like to declared this VM method "GetProductStatus()" to be
called only when a button click event occurs.
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
For above code you posted, we can find that the constructor of your viewmodel will be called as soon as you call code new ProductPage(_ViewModel).
So, you can try to remove code GetProductStatus(); in constructor ProductViewModel
public ProductViewModel(INavigation nav, Store store)
{
_Nav = nav;
// remove code here
//GetProductStatus();
}
and add a command in your ViewModel, and bind it to the button in your page.
Please refer to the following code:
public class ProductViewModel
{
public Command LoadDataCommand { get; set; }
public ProductViewModel() {
LoadDataCommand = new Command(loadData);
// remove code here
//GetProductStatus();
}
private void loadData()
{
GetProductStatus(); // add your code here
}
private async void GetProductStatus()
{
// other code
}
}
Note:
1.In this condition, you can also navigate as follows:
private async void ProductTypeButton_Clicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new ProductPage(_ViewModel));
}
2.I don't add parameter to the constructor of ProductViewModel , you can modify above code I posted according to your needs.
Set aside the fact that you are working with views and models. Simply think of them like any other class in c#.
If you need to tell class A "do something under these circumstances`, what are your options?
Pass a parameter in constructor: public ProductViewModel(..., bool doGetProductStatus)..., usage: new ProductViewModel(..., true);
Call a method A.DoSomething(); after you've created it: _ViewModel.DoSomething();
Use MessagingCenter Publish/Subscribe.
My WebForms project uses an NHibernate class library as an OR mapper to our Oracle databases. The NHibernate library has been fully tested and is working without any issues.
Global.asax.cs:
void Application_Start(object sender, EventArgs e)
{
RouteConfig.RegisterRoutes(RouteTable.Routes);
var cnnConfig = ""; //contains connection string information for NH
NHibernateClassLibrary.init(cnnConfig); //initializes NHibernate
var profileType = typeof(AutoMapper.Profile);
// Get an instance of each Profile in the executing assembly.
var profiles = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => profileType.IsAssignableFrom(t)
&& t.GetConstructor(Type.EmptyTypes) != null)
.Select(Activator.CreateInstance)
.Cast<Profile>();
// Initialize AutoMapper with each instance of the profiles found.
Mapper.Initialize(a => profiles.ForEach(a.AddProfile)); //ForEach is an extension method
}
ExtensionMethods.cs:
public static class ExtensionMethods
{
public static void ForEach<T>(this IEnumerable<T> enumerable,
Action<T> action)
{
foreach (T item in enumerable) { action(item); }
}
}
SampleProfile.cs:
namespace MyProjectName.AutomapperProfiles
{
public class EntityOneProfile : Profile
{
protected override void Configure()
{
base.Configure();
Mapper.CreateMap<NHibernateClassLibrary.Entities.EntityOne, EntityOnePoco>();
}
}
}
EntityOne.cs:
namespace NHibernateClassLibrary.Entities
{
public class EntityOne
{
public virtual string PropertyOne { get; set; }
public virtual string PropertyTwo { get; set; }
public virtual string PropertyThree { get; set; }
}
}
EntityOnePoco.cs:
namespace MyProjectName.GridEntities
{
public class EntityOnePoco
{
public string PropertyOne { get; set; }
public string PropertyTwo { get; set; }
public string PropertyThree { get; set; }
}
}
MyPage.aspx.cs:
namespace MyProjectName
{
public partial class MyPage : System.Web.UI.Page
{
//This gets us access to the NHibernate Class Library
IDATABASE DataBase = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IDATABASE>();
protected void Page_Load(object sender, EventArgs e)
{
var EntityOneRaw = DataBase.EntityOne.ToList();
var EntityOneObjects = EntityOneRaw.Select(q => AutoMapper.Mapper.Map<NHibernateClassLibrary.Entities.EntityOne, EntityOnePoco>(q)).ToList();
GridViewObject.DataSource = new BindingList<EntityOnePoco>(EntityOneObjects);
GridViewObject.DataBind();
}
protected void ButtonOne_Click(object sender, EventArgs e)
{
var Item = DataBase.EntityOne.First();
Item.PropertyTwo = "New Value";
DataBase.SaveChanges(); //NHibernate call to commit
}
}
}
MyPage.aspx:
<!--Other lines truncated-->
<dx:ASPxGridView ID="GridViewObject" runat="server" KeyFieldName="PropertyOne">
</dx:ASPxGridView>
<asp:Button ID="ButtonOne" runat="server" Text="Take Action" OnClick="ButtonOne_Click" />
The page_load method works fine. The grid will load with the database's current values, with the first entry's "PropertyTwo" equal to "Old Value" for example. If I press ButtonOne, the ButtonOne_Click method will fire and update the first entry in the EntityOne table appropriately. I've confirmed this directly in Oracle. Further, a breakpoint set right after this action completes shows that the NHibernateClassLibrary is appropriately fetching the new property. However, the GridView is still displaying "Old Value" until I stop IIS/Debugging and restart.
My first instinct is that caching is enabled somewhere, but I've confirmed NHibernate is not caching the old value. If EntityOne from the database is up to date, how do I force the AutoMapped EntityOnePoco to update at the same time? Am I missing something easy here?
The solution I found was to call DataBase.Clear(), essentially clearing the first-level NHibernate cache right before populating the EntityOneRaw object, as in the following example. My use-case benefits from first-level caching, so in my actual code, I only call the Clear() method as necessary.
namespace MyProjectName
{
public partial class MyPage : System.Web.UI.Page
{
//This gets us access to the NHibernate Class Library
IDATABASE DataBase = Microsoft.Practices.ServiceLocation.ServiceLocator.Current.GetInstance<IDATABASE>();
protected void Page_Load(object sender, EventArgs e)
{
DataBase.Clear(); //clear the first-level cache in NHibernaet
var EntityOneRaw = DataBase.EntityOne.ToList();
var EntityOneObjects = EntityOneRaw.Select(q => AutoMapper.Mapper.Map<NHibernateClassLibrary.Entities.EntityOne, EntityOnePoco>(q)).ToList();
GridViewObject.DataSource = new BindingList<EntityOnePoco>(EntityOneObjects);
GridViewObject.DataBind();
}
protected void ButtonOne_Click(object sender, EventArgs e)
{
var Item = DataBase.EntityOne.First();
Item.PropertyTwo = "New Value";
DataBase.SaveChanges(); //NHibernate call to commit
}
}
}
I'm trying to build this project using Caliburn for the first time (and also the MEF structure, that I didn't fully understand).
I need to use both the Conductor and the EventAggregator.
The Conductor because i have an AppViewModel which "displays" 3 buttons that move the user to 3 different views (UserControls inside AppView).
And I need the EventAggregator because one of these 3 views has a button inside of it that must load 4th view (that must be a Window I think, not a UserControl, because it has to be full screen).
So I thought that when the user click this button inside the 3 view (UserControl inside AppView) a Message can be sent top the listener (that should be the AppViewModel), and this one should ActivateItem(4th vm).
I don't why but even following the examples of the projects of Caliburn my message does not reach the AppViewModel.
This is my bootstrapper:
public class AppBootstrapper : Bootstrapper<AppViewModel>
{
private CompositionContainer container;
protected override void Configure()
{
container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x =>
new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(container);
container.Compose(batch);
}
protected override object GetInstance(Type serviceType, string key)
{
string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = container.GetExportedValues<object>(contract);
if (exports.Any())
return exports.First();
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
}
protected override void BuildUp(object instance)
{
container.SatisfyImportsOnce(instance);
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<AppViewModel>();
}
}
This is the AppViewModel:
[Export (typeof(AppViewModel))]
public class AppViewModel : Conductor<object>, IHandle<ChangeViewEvent>
{
[ImportingConstructor]
public AppViewModel(IEventAggregator events)
{
events.Subscribe(this);
ActivateItem(new MainViewModel());
}
public void GoToPatientsManager()
{
ActivateItem(new PatientsManagerViewModel(new WindowManager(), new EventAggregator()));
}
public void GoToTestManager()
{
ActivateItem(new TestManagerViewModel(new WindowManager()));
}
public void GoToResultsManager()
{
ActivateItem(new MainViewModel());
}
public void Handle(ChangeViewEvent message)
{
switch (message.ViewName)
{
case "TestManager" :
GoToTestManager();
break;
}
}
}
And this is the view model that should launch the request for loading the 4th vm
[Export(typeof(PatientsManagerViewModel))]
public class PatientsManagerViewModel : Screen
{
private readonly IWindowManager _windowManager;
private readonly IEventAggregator eventAggregator;
[ImportingConstructor]
public PatientsManagerViewModel(IWindowManager windowManager, IEventAggregator eventAggregator)
{
_windowManager = windowManager;
this.eventAggregator = eventAggregator;
}
#region Methods
public void ShowFakeMessage()
{
dynamic settings = new ExpandoObject();
settings.Placement = PlacementMode.Center;
settings.PlacementTarget = GetView(null);
var res = _windowManager.ShowDialog(new DeletePersonViewModel(), null, settings);
if (res)
{
// The result of the dialog men. In this true case we'll use Linq to delete the entry from the database
// using the dbContext
}
}
public void GoToTestManager()
{
eventAggregator.Publish(new ChangeViewEvent("TestManager"));
}
#endregion
}
It does not reach the Handle method of the AppViewModel.
Is these something wrong with the instances of the view models? I can't move forward from here...
EDIT
Can it be that the problem is that I pass a new EventAggregator object each time I activate a new PatientsManagerViewModel? Any tips?
You diagnosed your own problem. You are creating the PatientsManagerViewModel yourself and passing in a new event aggregator each time. The idea is that the event aggregator must be a singleton ie one instance shared amongst all of the view models and everything else. This is because the event aggregator stores the subscribers in memory and a new instance won't know who to inform of new events because nobody will have subscribed.
I would suggest that your AppViewModel.GoToPatientsManager() should look something like this:
public void GoToPatientsManager()
{
var patientManagerViewModel = IoC.Get<PatientsManagerViewModel>();
ActivateItem(patientManagerViewModel);
}
IoC is a (quite an ugly, hard to test) way you can access your container. You should not be creating your view models yourself, you should be getting the caliburn container do that for you instead. If the container creates your view models, then it will also fix up any dependencies that it has for you too, including the window manager and event aggregator.
We are using caliburn.micro for one of our projects and I'm currently having a puzzling problem:
we have the following classes:
ToolViewerViewModel : Conductor<Screen>.Collection.OneActive
DocViewerViewModel : Conductor<DocumentViewModel>
and various document-views, all with this base class:
DocumentViewModel : Screen
The ToolViewerViewModel is to manage multiple dock-able tool views which allow the user to control different aspects of the program.
The DocViewerViewModel is to show the user the data he's working on/with. It is here to present one of the many DocumentViewModel to the user. and is implemented as a special dock-able view which can not be closed or detached from the ToolViewerView. For every aspect of the data a specific DocumentViewModel is generated by the DocViewerViewModel and presented to the user.
The DocumentViewModel is the base class for all presentation aspects of the data. One may present the data as a table an other may present it as a chart, and so on...
We now encounter problems in terms of OnActivate() and OnDeactivate() which are not called when we expect them to be called.
First Problem:
The system is up and running; The DocumentViewModel is displayed in the DocViewerViewModel which is embedded in the ToolViewerViewModel along with one or two other dock-able views. The currently selected dock-able view is the DocViewerViewModel. When the user now selects one of the other dock-able views the OnDeactivate() method from the DocumentViewModel is being called. Which makes absolutely no sense to me. I'd expect the DocViewerViewModel.OnDeactivate() to be called.
Second Problem:
The system is up and running; The DocumentViewModel is displayed in the DocViewerViewModel which is embedded in the ToolViewerViewModel along with one or two other dock-able views. The currently selected dock-able view is the view that enables the user to change the DocumentViewModel presented by the DocViewerViewModel. When the user now selects an other DocumentViewModel the following code is being executed within the DocViewerViewModel:
DocViewerViewModel.DeactivateItem(oldDocumentViewModel, true);
DocViewerViewModel.ActivateItem(new DocumentViewModel());
I'd expect the DocumentViewModel.OnDeactivate() to be called upon the DocViewerViewModel.DeactivateItem(oldDocumentViewModel, true) call. but that never happens.
Conclusion:
The only proper working Conductor is the ToolViewerViewModel which is managing everything. But this behavior is not what we want or expect to happen: We'd like to have the ToolViewerViewModel only Conduct the dock-able views and the DocViewerViewModel to conduct the DocumentViewModel. This is important because there are two different use cases in place: One to manage multiple instances at the same time and the other where only one instance is active and used, the old instance shall be thrown away.
Hopefully anyone here can help me to get the behavior I'm looking for.
I Now have an example code for you:
public class ToolViewerViewModel : Conductor<Screen>.Collection.OneActive
{
private readonly IDockManager _dockManager;
private readonly DocViewerViewModel _docViewerViewModel;
private readonly IList<DockableViewModel> _toolViews = new List<DockableViewModel>();
public ToolViewerViewModel(IViewModelFactory viewModelFactory, DocViewerViewModel docViewerViewModel, IDockManager dockManager)
{
_dockManager = dockManager;
_viewModelFactory = viewModelFactory;
_docViewerViewModel = docViewerViewModel;
}
protected override void OnViewLoaded(object view)
{
_dockManager.Link(this);
_dockManager.CreateSpecialPaneFor(_docViewerViewModel);
ActivateItem(_docViewerViewModel);
ShowToolView<ProjectExplorerViewModel>();
base.OnViewLoaded(view);
}
public void ShowToolView<T>() where T : DockableViewModel
{
if (!IsToolViewOpen<T>())
{
var viewModel = _viewModelFactory.Create<T>();
ActivateItem(viewModel);
RefreshMenu(typeof(T));
}
}
}
Next class:
public class DocViewerViewModel : Conductor<DocumentViewModel>
{
private readonly IViewModelFactory _viewModelFactory;
public DocViewerViewModel(IViewModelFactory viewModelFactory)
{
_viewModelFactory = viewModelFactory;
}
public bool ShowInMainView<T>() where T : DocumentViewModel
{
return ShowInMainView(typeof(T));
}
private bool ShowInMainView(Type viewModelType)
{
var ret = false;
// close the current view
if (ActiveItem != null)
{
DeactivateItem(ActiveItem, true); //The close flag is on true since we want to remove the current instance from the memory
}
// check whether the current viewModel has been closed successfully
if (ActiveItem == null)
{
try
{
var viewModel = _viewModelFactory.Create(viewModelType) as DocumentViewModel;
if (viewModel != null)
{
ActivateItem(viewModel);
ret = true;
}
else
{
ActivateItem(_viewModelFactory.Create<NoDataViewModel>());
}
}
catch (Exception ex)
{
ActivateItem(_viewModelFactory.Create<NoDataViewModel>());
}
}
return ret;
}
}
and the last one:
public abstract class DocumentViewModel : Screen
{
private bool _isDirty;
protected IViewModelFactory ViewModelFactory { get; private set; }
protected IEventAggregator EventAggregator { get; private set; }
public bool IsDirty
{
get
{
return _isDirty;
}
protected set
{
if (value.Equals(_isDirty))
{
return;
}
_isDirty = value;
NotifyOfPropertyChange(() => IsDirty);
}
}
protected DocumentViewModel(IViewModelFactory viewModelFactory, IEventAggregator eventAggregator)
{
ViewModelFactory = viewModelFactory;
EventAggregator = eventAggregator;
}
protected override void OnDeactivate(bool close)
{
if (close)
{
if (EventAggregator != null)
{
EventAggregator.Unsubscribe(this);
}
}
base.OnDeactivate(close);
}
protected override void OnActivate()
{
if (EventAggregator != null)
{
EventAggregator.Subscribe(this);
}
base.OnActivate();
}
public override void CanClose(Action<bool> callback)
{
var ret = true;
if (IsDirty && (ViewModelFactory != null))
{
var saveDialog = ViewModelFactory.Create<SaveDialogViewModel>();
saveDialog.Show();
if (saveDialog.DialogResult == DialogResult.Cancel)
{
ret = false;
}
else
{
if (saveDialog.DialogResult == DialogResult.Yes)
{
Save();
}
else
{
Discard();
}
IsDirty = false;
}
}
callback(ret);
}
public abstract void Save();
public virtual void Discard()
{
}
}
With this code the only time the DocumentViewModel.OnDeactivate() is being called when the user brings an other dock-able view into focus while the DocViewerViewModel was having the focus. This should not happen!
When the user is changing the focus between the dock-able views the DocumentViewModel.OnDeactivate() should not get call. But it must get called when ever the Method DocViewerViewModel.ShowInMainView<SomeDocumentViewModel>() is being called. Which isn't the case currently.
As far as I can tell, there is nothing wrong with the way your code is written. Since you are using MVVM, I suggest you design a test case like I've provided here.
And here's a snippet of the test case
// TestHarness.cs
[TestMethod]
public void CheckDeactivation()
{
// We'd like to have the ToolViewerViewModel only Conduct the dock-able views
// and the DocViewerViewModel to conduct the DocumentViewModel.
IViewModelFactory factory = new ViewModelFactory();
DocViewerViewModel docViewer = new DocViewerViewModel(factory);
IDockManager dockManager = null;
var toolViewer = new ToolViewerViewModel(factory, docViewer, dockManager);
var mockToolView = new UserControl();
(toolViewer as IViewAware).AttachView(mockToolView);
DocumentViewModel docView1 = new NoDataViewModel();
DocumentViewModel docView2 = new NoDataViewModel();
docViewer.ActivateItem(docView1);
docViewer.ActivateItem(docView2);
Assert.AreEqual(0, docViewer.CountDeactivated());
}
I have had the exact same problem as you, and ended up using PropertyChangedBase instead of Screen and got the problem to disappear.
Later, after reading the docs on Screens and Conductors here, I realized that I wasn't activating the conductor itself further up in the view hierarchy!
So have a look at wherever you use your ToolViewerViewModel, and make sure you activate that instance!
Thank you very much for your Test. Even thought it is really nice code it tests the wrong code part. Your code simply tests whether the Method ActivateItem() or DeactivateItem() is being called:
public override void ActivateItem(DocumentViewModel item)
{
_countActivated++;
base.ActivateItem(item);
}
public override void DeactivateItem(DocumentViewModel item, bool close)
{
_countDeactivated++;
base.DeactivateItem(item, close);
}
But since these Methods are being called explicitly we don't need to test for that...
The real Problem is that the Conductor is not calling the OnActivate() or OnDeactivate() on the DocumentViewModel class. To enhance your test I used the following code:
public class DummyViewModelFactory : IViewModelFactory
{
private readonly Dictionary<Type, Func<object>> _registredCreators = new Dictionary<Type, Func<object>>();
public T Create<T>() where T : PropertyChangedBase
{
return Create(typeof(T)) as T;
}
public object Create(Type type)
{
if (type == null)
{
return null;
}
if (_registredCreators.ContainsKey(type))
{
return _registredCreators[type]();
}
return null;
}
public void Release(object instance)
{
}
public void RegisterCreatorFor<T>(Func<T> creatorFunction)
{
_registredCreators.Add(typeof(T), () => creatorFunction());
}
}
As concrete DocumentViewModel implementation I made:
public class NoDataViewModel : DocumentViewModel
{
public NoDataViewModel(IEventAggregator eventAggregator,
IViewModelFactory viewModelFactory)
: base(viewModelFactory, eventAggregator, )
{
}
public override void Save()
{
// nothing to do
}
public override void Reload()
{
// nothing to do
}
}
public class NoDataViewModelMock : NoDataViewModel
{
private static int activationCounterForTesting = 0;
private static int deactivationCounterForTesting = 0;
public static int ActivationCounterForTesting
{
get
{
return activationCounterForTesting;
}
}
public static int DeactivationCounterForTesting
{
get
{
return deactivationCounterForTesting;
}
}
public NoDataViewModelMock()
: base(null, null)
{
}
protected override void OnActivate()
{
activationCounterForTesting++;
base.OnActivate();
}
protected override void OnDeactivate(bool close)
{
deactivationCounterForTesting++;
base.OnDeactivate(close);
}
}
And I changed your Testmethod to this:
[TestMethod]
public void CheckDeactivation()
{
var viewModelFactory = new DummyViewModelFactory();
viewModelFactory.RegisterCreatorFor<NoDataViewModel>(() => new NoDataViewModelMock());
var docViewer = new DocViewerViewModel(viewModelFactory);
IDockManager dockManager = null;
var toolViewer = new ToolViewerViewModel(viewModelFactory, docViewer, dockManager);
var mockToolView = new UserControl();
(toolViewer as IViewAware).AttachView(mockToolView);
docViewerViewModel.ShowInMainView<NoDataViewModel>();
docViewerViewModel.ShowInMainView<NoDataViewModel>();
docViewerViewModel.ShowInMainView<NoDataViewModel>();
Assert.AreEqual(3, NoDataViewModelMock.ActivationCounterForTesting);
Assert.AreEqual(2, NoDataViewModelMock.DeactivationCounterForTesting);
}
Then you'll see that the OnActivate() and OnDeactivate() methods are never been called.
With a little more advanced test you'd also see that they are being called but from the ToolViewerViewModel directly. I'd like to know why and how I can change this behavior to fit my needs:
The DocumentViewModel.OnActivate() method should get called when the DocViewerViewModel.ShowInMainView<T>() method gets called.
The DocumentViewModel.OnDeactivate() method should get called on the old DocumentViewModel when a new one is being shown by calling the DocViewerViewModel.ShowInMainView<T>()
Our Solution for that Problem is to remove the use Screen as BaseClass for DocViewerViewModel an implement the Conductor Logic our self.
This was the question asked in an interview.
There is a Label with a property Text
In one page a label is simple Label, in other pages it may handle any one or combination of the below actions
Clickable
Resizable
Draggable
How do you design this label component that applies OOP design Principle & Design Pattern?
I said that I would create the following:
public class Label
{
public string Text{get;set;}
}
public interface IClickable
{
void Click();
}
public interface IDraggable
{
void Drag();
}
public interface IResizable
{
void Resize();
}
So that if the client want Resizable Label
public class ResizableLabel:Label,IResizable
{
....
}
same way ClickableLable, DraggableLabel
However, I feel that this is the incorrect approach, because I do not want to add those concrete classes. I want to avoid having ClickableAndDraggableLabel or ClickableDraggableResizableLabel.
Is there any design pattern that would solve this problem without adding these concrete classes?
I would use Decorator pattern. It is used extensivelly in .net world for different kind of streams, that allow you to compose encrypted, zipped, text stream wrappers for byte stream, for example. class diagram is taken from wiki
Example for you situation is not so trivial in implementation, but usage doen't require another classes for new compising behavior:
// Define other methods and classes here
public class Label
{
public string Text{get;set;}
public virtual void MouseOver(object sender, EventArgs args) { /*some logic*/ }
public virtual void Click(object sender, EventArgs args) { /*some logic*/ }
//other low level events
}
public class ClikableLabel : Label
{
private Label _label;
public ClikableLabel(Label label)
{
_label = label;
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
public class DraggableLabel : Label
{
private Label _label;
public DraggableLabel(Label label)
{
_label = label;
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
public class ResizableLabel : Label
{
private Label _label;
public ResizableLabel(Label label)
{
_label = label;
}
public override void MouseOver(object sender, EventArgs args)
{
//specific logic
_label.MouseOver(sender, args);
}
public override void Click(object sender, EventArgs args)
{
//specific logic
_label.Click(sender, args);
}
}
now you can
var clickableDragableLabel = new ClikableLabel(new DraggableLabel(new Label{Text = "write me!"}));
var makeItResizable = new ResizableLabel(clickableDragableLabel);
I don't think Interface can resolve your problem.
I would make something more like this:
First, define an enum which list all your action:
public Enum LabelAction{ None = 0, Clickable = 1, Resizable = 2, Draggable = 4 }
For having multiple Enum defined, you can look this links:
How do you pass multiple enum values in C#?
Enumeration Types as Bit Flags
Then define a member in your class Label, taking an action:
public class Label
{
private readonly LabelAction _action;
private string Text { get; set; }
public class Label(string text)
: Label(text, LabelAction.None) { }
public class Label(string text, LabelAction action)
{
this.Text = text;
this._action = action;
}
public bool CanClick
{
get
{
return this._action & LabelAction.Clickable == LabelAction.Clickable;
}
}
public bool CanResize { get { return this._action & LabelAction.Resizable == LabelAction.Resizable ;} }
public bool CanDrag { get { return this._action & LabelAction.Draggable == LabelAction.Draggable ;} }
public Click()
{
if(this.CanClick) { /* click */ }
else { throw new Exception("Not clickable");}
}
public Drag()
{
if(this.CanDrag) { /* drag */ }
else { throw new Exception("Not draggable");}
}
public Resize()
{
if(this.CanResize) { /* resize */}
else { throw new Exception("Not resizable");}
}
}
Usage:
var simpleLabel = new Label("simple");
var clickable = new Label("clickable", LabelAction.Clickable);
var clickableDraggable = new Label("clickable and draggable", LabelAction.Clickable | LabelAction.Draggable);
public void DoEvent(Label label)
{
if(label.CanClick) label.Click();
if(label.CanDrag) label.Drag();
if(label.CanResize) label.Resize();
}
If you need to add an action, you will have to add one item to the Enum LabelAction, one method CanDo() and one method Do() to the Label class. Not so much so.
I would just have boolean properties for CanClick, drag, and resize, all default to true, and falsed as required (or as inherited).
constructor as follows
public Label(bool canClick = true, bool canDrag = true, bool canResize = true){}
Chances are if they're extending a class once, its going to be extended further at a later date
Well you can have a base class that implement all the interface and delegate their behaviour to concretes strategy classes.
Then you would have a NullDraggable, nulResizeable,NullClickable by default that do nothing (so your base label is not clickable, resizable and dragrable)
Then you create different strategy, like Clickable, DoubleClickable, WidthResizeable etc...
You then pass the combination you want to your class.
This way you obtain a lot of little strategy that are easy to reuse in other component with the same interface.
You can have multiple behaviour by using a composite pattern (for example you can have clickable and doubleclickable togheter)
This probably would be a little too ingeneered though
I think you're over-thinking what the interviewer must have had in his mind. If the case is as simple and practical, to avoid the complexity of over abstraction, then this would suffice:
public class Label
{
public string Text{get;set;}
}
public class ComlexLabel : Label
{
Click();
Drag();
Resize();
}
You can do any operation on it. Now if for a challenge you need only one concrete instance and need separate type of objects to be able to do only a combination of these things, its again simple - only that this time you have to create similar prototypes/interfaces:
public class Label
{
public string Text{get;set;}
}
public interface Clickable
{
Click();
}
public interface Resizable
{
Resize();
}
public interface Dragable
{
Drag();
}
public interface ClickableDragable : Clickable, Draggable
{
}
public interface ClickableResizable : Clickable, Resizable
{
}
public interface ResizableDragable : Resizable, Draggable
{
}
public interface ClickableDragableResizeable : Resizable, Clickable, Draggable
{
}
public class ComlexLabel : Lable, ClickableDragableResizeable
{
Click();
Drag();
Resize();
}
Now you can have instances of ComlexLabel by making the type that gives the required feature. Like:
ResizableDragable rd = new ComlexLabel();
ClickableResizable cr = new ComlexLabel();
ClickableDragableResizeable cdr = new ComlexLabel();
Now rd, cr and cdr have different capabilities. And only one concrete instance behind them. To prevent the clients from getting full privilege by doing
var cdr = new ComplexLabel();
you should make the ComplexLabel constructor private and assign the task to some factory. Like
var rd = Factory.GetResizableDragableLabel();
Now rd must be just ResizableDragable with no Click functionality..
I think for this scenario, there is no need to re-invent the wheel. Even though the question explicitly asks for OOP it is not explicitly asking you to ignore Component Model programming nor event based behaviors.
That's why I would follow an approach that allows a division of responsibilities where Label is responsible to notify when it is being clicked or dragged and SomeOtherComponent might or might not listen to such notification (event) in order to perform other logic.
Please take a look at the links below for examples of the approach of event dispatching for those user actions :
Drag and Drop
Label Class
Regards,