Caliburn Micro Screen Derived View Model Support Sub View Models? - windows-runtime

Using Caliburn Micro v2 is there a way of composing view models inside a screen such that the child view models get the OnInitialize, OnActivate() and OnDeactivate(bool) calls?
I have something like the following view model WidgetsViewModel used to display a screen.
public class WidgetsViewModel : Screen, IHandle<WidgetUpdateEvent>
{
public ObservableCollection<WidgetSummaryViewModel> Widgets { get; set; }
...
public void Handle(WidgetUpdateEvent theEvent)
{
// Update the specific widget in Widgets collection
}
}
public class WidgetSummaryViewModel
{
public int Counter { get; set; }
}
I would like to move the handling of the WidgetUpdateEvent into the WidgetSummaryViewModel where it would sit more comfortably. Something like this:
public class WidgetSummaryViewModel : IHandle<WidgetUpdateEvent>
{
public int Counter { get; set; }
public WidgetSummaryViewModel(IEventAggregator theEventAggregator)
{
theEventAggregator.Subscribe(this);
}
public void Handle(WidgetUpdateEvent theEvent)
{
// Update this view model...
}
}
What makes me uncomfortable is calling Subscribe inside the view model constructor. It would be much better if the screen was able to call the OnInitialize, OnActivate and OnDeactivate for me as it does for Screen derived view models. Is there some way to compose sub view models inside a screen derived based view model?

Make WidgetsViewModel a conductor:
public class WidgetsViewModel : Conductor<WidgetSummaryViewModel>.Collection.AllActive
{
}
Update your data binding in WidgetsView:
{Binding Items} instead of {Binding Widgets}
Derive WidgetSummaryViewModel from Screen and override all the methods you need.
For further information regarding Conductors, visit Screens, Conductors and Composition.

Related

Bind property from View to ViewModel

I have a property MyFunc in my custom view:
public class MyView : MvxLinearLayout
{
public Func<Task<byte[]>> MyFunc { get; set; }
}
I would like to call this function from the ViewModel. How can I bind to it so I can have it in my ViewModel?
public class MyViewModel : MvxViewModel
{
public Func<Task<byte[]>> MyFunc { get; set; } // Bind to it here so I can call it within this class
}
You can't bindings work the other way around.
Source is always ViewModel
Target is always View
I don't know what you want to achieve with this. However, it looks like you want to populate your LinearLayout with images or some kind of byte data.
For that, use a Command. If you really want a platform specific function to trigger in your shared code, you can use MvxInteraction.

Apply aspect to all methods/actions in a class

I'm working on an ASP.NET MVC 5, I want to log all exceptions that occurs in the controller's actions.
To accomplish this I'm creating a custom aspect using PostSharp (in a dll), there I've already created the code to write the log files, now I want that the aspect can be controller-wide (do not want to apply it by hand to all methods).
The aspect's code looks like this:
using System;
using PostSharp.Aspects;
namespace Banlinea.Ceb.Domain.Aspects
{
public class LogException : OnExceptionAspect
{
public LogException()
{
ApplyToStateMachine = true;
}
public override void OnException(MethodExecutionArgs args)
{
//Code for logging the exception
args.FlowBehavior = FlowBehavior.ThrowException;
}
}
}
Now, what I want in my controller is to do something like this:
[LogException]
public class MyController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Other()
{
return View();
}
public ActionResult Another()
{
return View();
}
}
Just decorate the class, How can I do that?
you can do this byimplementing IAspectProvider
http://doc.postsharp.net/iaspectprovider
public IEnumerable<AspectInstance> ProvideAspects(object targetElement)
{
Type type = (Type)targetElement;
return type.GetMethods().Select(
m => return new AspectInstance(targetElement, new LogException()) );
}
You can apply PostSharp aspects across your codebase by using a feature called attribute multicasting.
For example, when you apply a method-level aspect on a class level or assembly level, then it is automatically copied to all the methods in the corresponding class or assembly. You can additionally filter the target elements by setting the attribute properties, such as AttributeTargetTypes, AttributeTargetMemberAttributes etc.
The sample code from your question should actually work as you expect.

mvvmcross - multiple Init methods in viewmodel with different signature not working

in a main viewmodel where i collect data from another viewmodels, I created in summary two or three public Init methods with different signatures. When i navigate back to the base viewmodel from the other viewmodels with ShowViewModel, I awaited that the right Init method will be executed, but this don't happen. Regarding the greet practical documentation here:
http://slodge.blogspot.ch/2013/03/v3-new-viewmodel-lifecycle.html
This should be work :-/.
I will explain this with some code.
My main view model is e.g.:
public class MainViewModel : MvxViewModel
{
MainViewModel() {}
public class ParameterFirst
{
public string Id { get; set; }
}
public class ParameterSecond
{
public string Id { get; set; }
}
public class ParameterSecond
{
public string Id { get; set; }
}
public class ParameterThird
{
public string Id { get; set; }
}
public void Init(ParameterFirst objFirst)
{
//do something
}
public void Init(ParameterSecond objSecond)
{
//do something
}
public void Init(ParameterThird objThird)
{
//do something
}
}
Then I will navigate from another viewmodel and await that the right Init method will be executed:
public class CollectData_ONE_ViewModel : MvxViewModel
{
CollectData_ONE_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterFirst { Id = "11" });
}
}
next here the second viewmodel
public class CollectData_SECOND_ViewModel : MvxViewModel
{
CollectData_SECOND_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterSecond { Id = "22" });
}
}
and the third viewmodel
public class CollectData_THIRD_ViewModel : MvxViewModel
{
CollectData_THIRD_ViewModel() {}
public void DidWork()
{
//Hopefully the Init method with argument ParameterFirst should be called
base.ShowViewModel<MainViewModel>(new MainViewModel.ParameterThird { Id = "33" });
}
}
In my code, each time the First Init method is called, I'm really at the end and don't have further ideas :) Did anyone here experienced the same issue? Or do anyone here have another Idea to collect data to the main viewmodel in an elegant way? Thanks a lot in advance for reading :)
The Init mechanism in MvvmCross is deliberately lightweight. If you declare multiple methods, all of them will be called - this is by design. Also if some of the Init parameter objects were to share properties then these would clash - see Custom types in Navigation parameters in v3
As it says in the blog post you reference "generally you will probably only want to use one within your application" - so I'd recommend refactoring to a single navigation parameter object and using your own ViewModel-based logic to decide how your ViewModel should initialise.
If you really do need three Init methods called in three different situations, then you can easily pack and unpack your own parameter objects using a custom method (possibly in a BaseViewModel class) like in https://stackoverflow.com/a/19059938/373321

Looking to initialise every view with a List no matter the entry point of the site

I have a Shopping Basket which holds selected items by the user and is stored in a session variable. I'd like this to show various values on every view about the basket state i.e. basket:1, but I can only see how to pass this to a view at a single entry point. How would I initialise every view with this List?
You could render a child action in your Layout. The idea of a child action is that it could execute some logic in parallel to the main action.
For example you could have the following controller:
public class ShoppingBasketInfoController: Controller
{
[ChildActionOnly]
public ActionResult Index()
{
var model = Session["info"] as ShoppingInfoViewModel;
return PartialView(model);
}
}
and then you will have a corresponding partial view (~/Views/ShoppingBasketInfo/Index.cshtml):
#model ShoppingInfoViewModel
<div>
You have #Html.DisplayFor(x => x.NbProducts) in your basket
</div>
and then you could find an appropriate place in your Layout to render this action:
#Html.Action("Index", "ShoppingBasketInfo")
Now all your views will have this information shown at the specified location without worrying about where this information is coming from, how is it stored or what view model it uses. The main actions are completely independent.
I have decorated the child action with the [ChildActionOnly] attribute to ensure that this action will never be accessible through a normal HTTP request from the client using for example /ShoppingBasketInfo/Index. It could only be used within the context of the main executing action.
Your best bet is probably a combination of Base Controller, Base View Model, Interface and Action Filter.
// Interface. To be implemented by model and controller.
public interface IHoldABasket
{
Basket Basket { get; set; };
}
// Base view model. Has a basket as public property.
public BaseBasketViewModel : IHoldABasket
{
public Basket Basket { get; set; }
}
// Base controller model. Also has a basket.
public BaseController : Controller, IHoldABasket
{
public Basket Basket { get; set; }
public BaseController()
{
AttemptBasketLoad();
}
private void AttemptBasketLoad()
{
// Replace the SomeMethodToLoadBasket with whatever method you use
// to retrieve a basket.
Basket = SomeMethodToLoadBasket();
}
}
// Action Filter
public class BasketAwareAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// If controller can hold basket AND model can hold basket
if (filterContext.Controller is IHoldABasket
&& filterContext.Controller.ViewData.Model is IHoldABasket)
{
// Copy basket from controller into model.
// Will now be accessible through Basket property on model.
((IHoldABasket)filterContext.Controller.ViewData.Model)
.LoggedInUser
= ((IHoldABasket)filterContext.Controller).LoggedInUser;
}
base.OnActionExecuted(filterContext);
}
}
So that's the infrastructure sorted. Let's look at a practical example. You probably have a ProductListViewModel. That should inherit from the base view model class.
First, make sure your ProductListViewModel inherits from BaseBasketViewModel.
public class ProductListViewModel : BaseBasketViewModel
{
}
Because of the inheritance, your view model contains a basket object and implements the IHoldABasket interface.
Your controllers will inherit from BaseController.
public class ProductController : BaseController
{
}
A controller method looks like this.
[BasketAware]
public ViewResult Products(int page = 1)
{
// Load VM that implements IHoldABasket
// Really contrived, I know... :P
var vm = new ProductListViewModel() { Results = productServices.Search() };
return View(vm);
}
That should be it. What's happening under the hood is
Base controller attempts to load a basket, storing it if it finds one
Controller and Model inherit a common interface, making the automated copy from controller to model easier to achieve.
Action filter loads at the last minute. If both controller and model can hold a basket (e.g. both implement IHoldABasket), the basket is copied from controller to model.
All view models that derive from BaseBasketViewModel will have a public property called Basket.

t4mvc : Cannot inherit a controller class which has no default constructor?

I am using T4MVC with MVC2.
I have the following building blocks:
A simple entity interface which defines that every POCO entity must have a long Id property:
public interface IEntity
{
public long Id;
}
A simple POCO class which implements the IEntity interface and has some string properties:
public class CD : IEntity
{
public long Id { get; set; }
public long Name { get; set; }
}
A base controller:
public abstract class EntityController<T> : Controller where T : class, global::IEntity
{
public EntityController(IEntityManager<T> manager);
}
I use this base controller in my CDController (where CDManager implements the IEntityManager interface, which is a UnitOfWork pattern to add CRUD functionality):
public partial class CDController : EntityController<CD>
{
public CDController() : base(new CDManager()) { }
}
When I run my t4 template, this code is generated:
namespace MyApp.Web.Controllers {
public partial class CDController {
[GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode]
protected CDController(Dummy d) { }
But this gives me an error during compilation:
MyApp.EntityController<CD> does not contain a constructor that takes 0 arguments
How can I solve this?
I wanted by controller base class to be abstract and it's constructor protected and parametrized. Got around this issue by adding a blank constructor to ControllerBase that throws a NotImplementedException.
Doesn't quite feel right but it gets the job done. Only issue is when combined with dependency injection the wrong constructor will be called - since it throws an exception the app will bum out.
Code:
public abstract class ControllerBase : Controller
{
protected object AlwaysSupply { get; private set; }
public ControllerBase()
{
throw new NotImplementedException();
}
public ControllerBase(object alwaysSupply)
{
AlwaysSupply = alwaysSupply;
}
}
This will cause T4MVC to generate compilable code. The fault seems to be it always tries to generate a blank (no parameters) constructor for controller classes.
Hope this helps someone.
I see the problem, and it comes down to T4MVC not quite doing the right thing when dealing with generic classes. Normally it would generate a default ctor for it in a partial class, but the fact that it's generic is throwing it off.
You should be able to work around simply by adding a default ctor yourself, e.g.
public abstract partial class EntityController<T> : Controller where T : class, IEntity {
public EntityController() { }
// etc...
}
I've noticed something very odd:
I've added the empty constructor to the base class, but without the throw new NotImplementedException(); and it works fine.
But here's the odd thing, when calling the controller if I have an url like
/{controller}?params (default action being set to Index in the RouteConfig) the parameterless private controller on the base class is called.
But when I have an url like /{controller}/{action}?params then the constructor with parameters is called.