How and Where to tell if a ViewComponent has been invoked x times in a view? - asp.net-core-mvc

I have a ViewComponent that I need to invoke twice only! How and where can I tell the invokations count?
Currently I can use a session but I dislike using session in mvc apps! How may I achieve this?
namespace Partials.Components
{
public class MyComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
Session["invoked"]=(int)Session["invoked"]+1;
var model = new{
Website="Stack Overflow",
Url="www.http://stackoverflow.com"
};
return View("_MyComponent ", model);
}
}
}
and in my view
#Component.Invoke("MyComponent")
<span>Invoked ViewComponent <span>#Session["invoked"]</span> times</span>

You can use TempData. It persists only until the next request.
TempData["invoked"]=(int)TempData["invoked"]+1;
View:
<span>Invoked ViewComponent <span>#TempData["invoked"]</span> times</span>
Note: TempData uses session under the covers.

You can use HttpContext.Items which has the advantage of not using the session. These items are stored and shared per request, which would also fit your objective.
In your viewComponent you can add/retrieve an item as in this.Context.Items["MyComponentInvocationCount"]. Whenever the count is greater than 2 you can just return an empty content with return Content(String.Empty).
You can combine that with an extension method so you can get the count from outside that class:
[ViewComponent(Name = "MyComponent")]
public class MyViewComponent : ViewComponent
{
internal static readonly string ContextItemName = "InvocationCount";
public IViewComponentResult Invoke()
{
this.InvocationCount = this.InvocationCount + 1;
if (this.InvocationCount > 2) return Content(String.Empty);
//return your content here
return Content("Can be invoked");
}
private int InvocationCount
{
get
{
return this.Context.InvocationCount();
}
set
{
this.Context.Items[ContextItemName] = value;
}
}
}
public static class MyViewComponentExtensions
{
public static int InvocationCount(this HttpContext context)
{
var count = context.Items[MyViewComponent.ContextItemName];
return count == null ? 0 : (int)count;
}
}
Then you could use it in a view as follows:
#Component.Invoke("MyComponent")
<span>Invoked ViewComponent <span>#Context.InvocationCount()</span> times</span>
If you add the above lines 3 times in a view, you will see that the third one does not add any content.
EDIT - Using ViewComponentInvoker
I have been exploring how to implement this feature adding a custom ViewComponentInvoker.
I started by adding a new attribute that can be used to decorate ViewComponents so they are limited to a certain number of invocations per request:
public class PerRequestInvocationLimitAttribute: Attribute
{
public int PerRequestInvocationLimit { get; set; }
}
You would then create your view component as usual, the only change being adding this attribute:
[PerRequestInvocationLimit(PerRequestInvocationLimit = 2)]
public class MyViewComponent : ViewComponent
{
//implementation of view component
}
We can then create a custom IViewComponentInvoker that decorates the DefaultViewComponentInvoker.
This custom view component invoker will keep track of the number of
times a view component has been invoked in the current request.
When a view component that has the new attribute is invoked, it will only
really invoke it if the number of invocations is below the limit.
Implementing this view component invoker looks like:
public class LimitedPerRequestViewComponentInvoker : IViewComponentInvoker
{
private readonly IViewComponentInvoker _defaultViewComponentInvoker;
public LimitedPerRequestViewComponentInvoker(IViewComponentInvoker defaultViewComponentInvoker)
{
this._defaultViewComponentInvoker = defaultViewComponentInvoker;
}
public void Invoke(ViewComponentContext context)
{
if (!CanInvokeViewComponent(context)) return;
this._defaultViewComponentInvoker.Invoke(context);
}
public Task InvokeAsync(ViewComponentContext context)
{
if (!CanInvokeViewComponent(context)) return Task.WhenAll();
return this._defaultViewComponentInvoker.InvokeAsync(context);
}
private bool CanInvokeViewComponent(ViewComponentContext context)
{
// 1. Increase invocation count
var increasedCount = context.ViewContext.HttpContext.IncreaseInvocationCount(
context.ViewComponentDescriptor.ShortName);
// 2. check if there is any limit for this viewComponent, if over the limit then return false
var limitAttribute = context.ViewComponentDescriptor.Type
.GetCustomAttributes(true)
.OfType<PerRequestInvocationLimitAttribute>()
.FirstOrDefault();
if (limitAttribute != null && limitAttribute.PerRequestInvocationLimit < increasedCount)
{
return false;
}
// 3. There is no limit set or the limit has not been reached yet
return true;
}
}
It uses some extension methods to set/get the invocation count from HttpContext.Items (That you could also use in your view to get the number of times a view component was invoked)
public static class ViewComponentExtensions
{
public static int InvocationCount(this HttpContext context, string viewComponentName)
{
var count = context.Items[GetHttpContextItemsName(viewComponentName)];
return count == null ? 0 : (int)count;
}
internal static int IncreaseInvocationCount(this HttpContext context, string viewComponentName)
{
var count = context.InvocationCount(viewComponentName);
context.Items[GetHttpContextItemsName(viewComponentName)] = ++count;
return count;
}
private static string GetHttpContextItemsName(string viewComponentName)
{
return string.Format("InvocationCount-{0}", viewComponentName);
}
}
The final piece is to create a new IViewComponentInvokerFactory replacing the default one, so it creates an instance of the new custom view component invoker instead of the default one. You also need to register it on Startup.cs:
public class MyViewComponentInvokerFactory : IViewComponentInvokerFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly ITypeActivatorCache _typeActivatorCache;
private readonly IViewComponentActivator _viewComponentActivator;
public MyViewComponentInvokerFactory(IServiceProvider serviceProvider, ITypeActivatorCache typeActivatorCache, IViewComponentActivator viewComponentActivator)
{
_serviceProvider = serviceProvider;
_typeActivatorCache = typeActivatorCache;
_viewComponentActivator = viewComponentActivator;
}
public IViewComponentInvoker CreateInstance(ViewComponentDescriptor viewComponentDescriptor, object[] args)
{
return new LimitedPerRequestViewComponentInvoker(
new DefaultViewComponentInvoker(_serviceProvider, _typeActivatorCache, _viewComponentActivator));
}
}
//Configure the ViewComponentInvokerFactory in Startup.ConfigureServices
services.AddTransient<IViewComponentInvokerFactory, MyViewComponentInvokerFactory>();
With all these pieces in place, you can use your view component 3 times and you will see how it will be rendered only twice:
#Component.Invoke("MyComponent")
<span>Invoked ViewComponent <span>#Context.InvocationCount("MyComponent")</span> times</span>
I prefer this solution for a few reasons:
It is based on the hooks provided by the new mvc framework.
Does not need changes to your view component, other than adding the attribute that sets the invocation limit.
It works when invoking view component asynchronously.

Related

BeforeSaveEntity get fired after save

I'm using Breeze with my Web API and I want to add some auditoring on my Entities. So I want to use the BeforeSaveEntityDelegate but when I implement it, it gets fired after the save function...
Here is my Breezecontroller:
[BreezeController]
public class EssController : ApiController
{
private readonly ESSContextProvider _contextProvider;
public EssController(ESSContextProvider contextProvider)
{
_contextProvider = contextProvider;
}
protected bool BeforeSaveEntity(EntityInfo entityInfo)
{
// create audit record and add to your instance of your context
// this.Context.YourAuditEntity.Add(...)
if (entityInfo.EntityState == EntityState.Modified)
{
var auditable = (Entity)entityInfo.Entity;
auditable.UpdatedBy = "jja";
auditable.UpdatedDate = DateTime.Now;
}
return true;
}
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
_contextProvider.BeforeSaveEntityDelegate = BeforeSaveEntity;
return _contextProvider.SaveChanges(saveBundle);
}
[HttpGet]
public string Metadata()
{
return _contextProvider.Metadata();
}
}
So first he execute SaveChanges and then enters BeforeSaveEntity...
I think there is no error in the sequence of calls. When you call _contextProvider.SaveChanges(saveBundle); the ContextProvider's SaveChanges method will call the BeforeSaveEntityDelegate if any, then it will persist the changes and call the AfterSaveEntities delegate.
Take a look a the source code for ContextProvider here https://github.com/IdeaBlade/Breeze/blob/master/Breeze.ContextProvider/ContextProvider.cs . Look for the "OpenAndSave" method inside the class.
Don't confuse your ApiController SaveChanges with ContextProvider's SaveChanges.

How do I persist the value of a property across page-refreshes?

I have created a property in app.xaml ( Public Static List id {get set} )
Can i add Data (Eg: App.id.Add(user.id)) to it.
So that i can take the added data from the page and use it any navigated page (Eg: App.id[3]).
Initializing is the problem when page refreshed the data goes back to App.id[0]
Alternativ you could use the Singleton pattern here, this will ensure that the list is created and only one instance exsist.
In your App.cs file write the following:
private static List<string> _id;
public static List<string> id
{
get
{
if (_id == null)
_id = new List<string>();
return _id;
}
set
{
_id = value;
}
}
Yes you can, public properties defined in app.cs can be reached from all your pages in your app.
in your App.cs
public List<String> id = new List<string>();
// Call this method somewhere so you create some data to use...
// eg in your App() contructor
public void CreateData()
{
id.Add("ID1");
id.Add("ID2");
id.Add("ID3");
id.Add("ID4");
id.Add("ID5");
}
When you need to use it your can get the data from eg. the OnNavigateTo event handler
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Sets the data context for the page to have a list of strings
this.DataContext = ((App)Application.Current).id;
// Or you can index to the data directly
var a = ((App)Application.Current).id[2];
}

ActionMethodSelectorAttribute unable to access types?

I am relatively new to MVC3, and am developing a website that will need to handle pre-loaded accounts in the default Microsoft membership provider, using SQL Server, EF4, etc. Some progress has been made, and with the help of someone on SO, I have got the ActionMethodSelectorAttribute working correctly to help me with that.
I.e., when we see someone's ID as part of their attempt to load a profile page (www.mysite.com/profile/4) we will look to see if that ID/account has been 'claimed' or not. (My original posting is here: MVC3 using routes or using controller logic?)
Unfortunately, inside the ActionMethodSelectorAttribute, I am having a heck of a time doing a relatively simple database call to determine if the account is claimed/not claimed.
Here is my current state of the code:
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
// get profile id first
int id = int.Parse((string)controllerContext.RouteData.Values["id"]);
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
bool isActivated = profile;// some code to get this state
return isActivated;
}
}
The line
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
errors on the db. section, with error message as follows:
Cannot access a non-static member of outer type 'MySite.Controllers.HomeController' via nested type 'MySite.Controllers.HomeController.UserAccountActivatedAttribute'
...with the error being highlight under the db.
Does anyone know why, inside the ActionMethodSelectorAttribute, I cannot seem to make this call? (NOTE: inside the same Home controller, I am making many similar calls in Public ActionResult and ViewResult classes without any errors.)
EDIT
My HomeController.cs looks like this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MySite.Models;
namespace MySite.Controllers
{
public class HomeController : Controller
{
private MySiteEntities db = new MySiteEntities();
public ActionResult Index()
{
ViewBag.Message = "Welcome to MySite.com!";
return View();
}
//several other ActionResults - create, delete, etc.
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
// get profile id first
int id = int.Parse((string)controllerContext.RouteData.Values["id"]);
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
bool isActivated = profile;// some code to get this state
return isActivated;
}
}
...definitely it falls inside the Home Controller.
EDIT #2:
Closer, but a small issue with the value always being TRUE.
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
private MySiteEntities db = new MySiteEntities();
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
int id = int.Parse((string)controllerContext.RouteData.Values["id"]);
var data = new MySiteEntities();
var claimed = db.Claimeds.FirstOrDefault(c => c.ProfileId == id);
bool isActivated = claimed.Claimed1.Value != null;
return isActivated;
}
}
The claimed.Claimed1.Value != null; gives me a warning: The result of the expression is always 'true' since a value of type 'bool' is never equal to 'null' of type 'bool?'
However, I have to have something there to handle a NULL value, right?
I think your code actually looks more like this, am I right?
public class HomeController : Controller
{
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
...
}
}
You need to make the attribute class a first-level class, not nested within the controller. You then need to give it its own db instance.
public class HomeController : Controller
{
...
}
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
private readonly CustomDbContext db = new CustomDbContext();
public override bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo)
{
// original code here
}
}
The reason for this is because when the attribute class is nested within the controller class, it cannot access the db instance variable, because it is not a static variable. Your attributes should really not be nested classes, and should have their own separate instance variables. In other words, don't try to solve this by making the controller's db variable static.

Why is an item in the ViewBag not propagated to the _Layout page?

I have a SiteNavigation class that is being updated in the Initialize event of my base controller e.g.
[Serializable]
public class SiteNavigation
{
public SiteNavigation()
{
IsSummarySelected = true;
}
public Model.Dtos.Folder[] Folders { get; set; }
public bool IsSummarySelected { get; set; }
}
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
var siteNavigation = new SiteNavigation();
siteNavigation.Folders = GetMeMyFolders() as Folder[];
ViewBag.SiteNavigation = siteNavigation;
}
and in the controller the IsSummarySelected property is changed to this value.
ViewBag.SiteNavigation.IsSummarySelected = false;
When I access the property in the _Layout file with this line of code, the value is ALWAYS true. It's as if the nav object is being New'd up again and the constructor is setting it to true.
#if (ViewBag.SiteNavigation.IsSummarySelected)
I've tried casting the nav object back to a variable and setting the property that way too, no dice. Any help would be appreciated.
Call me baffled!
Thank you,
Stephen
I just copy pasted your code into my sample mvc project, and changing IsSummarySelected in my action correctly was reflected in the _Layout file. Are you certain your controller's assignment is getting hit, and you're not reassigning it afterwards somewhere else?
Edit: Your issues are an example of why I think it's a bad idea to use ViewBag for anything other than a localized quick fix. Debugging dynamic global objects is no fun. Refactoring suggestion: Make a site Navigation property in your base controller
SiteNavigation siteNavigation;
public SiteNavigation SiteNavigation
{
get
{
return siteNavigation;
}
set
{
siteNavigation = value;
}
}
and replace all references to ViewBag.SiteNavigation with this. Then create a custom WebViewPage and put in it.
public SiteNavigation SiteNavigation
{
get
{
return ((BaseController)ViewContext.Controller).SiteNavigation;
}
}
This won't fix your problem, but now you can just stick breakpoints on the get and set properties of SiteNavigation, and it should be very easy to debug your issue now.
I fill my TempData["SplitterIsCollapsed"] when Filters are invoked then via OnResultExecuting method. Additionally i fetch a property state from my UserContext class, which is registered only once per session: builder.RegisterType().As().CacheInSession(); .
Basic info: I use DependcyInjection!
Assignment of the Filter to the Controller:
Controller:
[LayoutTempData]
public class HomeController : Controller
{
//....
}
FilterAttribute class:
namespace MyProject.Web.Infrastructure.Filters
{
public class LayoutTempDataAttribute : ActionFilterAttribute
{
private readonly IUserContext _userContext;
public LayoutTempDataAttribute()
{
_userContext = DependencyResolver.Current.GetService<IUserContext>();
}
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.Controller.TempData.ContainsKey("SplitterIsCollapsed"))
context.Controller.TempData["SplitterIsCollapsed"] = _userContext.LayoutInformation.SplitterIsCollapsed;
else
context.Controller.TempData.Add("SplitterIsCollapsed", _userContext.LayoutInformation.SplitterIsCollapsed);
}
}
}
The Splitter part of the _Layout.cshtml looks like:
#{Html.Telerik().Splitter().Name("Splitter1")
.Panes(panes =>
{
panes.Add()
.Size("300px")
.Collapsible(true)
.Collapsed((bool)TempData["SplitterIsCollapsed"])
.Content(<div>asdfasdf</div>);
panes.Add()
.Collapsible(false)
.Scrollable(false)
.Content(<div>content2</div>);
})
.Render();
}

Validation in a Domain Driven Design

How do you deal with validation on complex aggregates in a domain driven design? Are you consolidating your business rules/validation logic?
I understand argument validation and I understand property validation which can be attached to the models themselves and do things like check that an email address or zipcode is valid or that a first name has a minimum and maximum length.
But what about complex validation that involves multiple models? Where do you typically place these rules & methods within your architecture? And what patterns if any do you use to implement them?
Instead of relying on IsValid(xx) calls all over your application, consider taking some advice from Greg Young:
Don't ever let your entities get into
an invalid state.
What this basically means is that you transition from thinking of entities as pure data containers and more about objects with behaviors.
Consider the example of a person's address:
person.Address = "123 my street";
person.City = "Houston";
person.State = "TX";
person.Zip = 12345;
Between any of those calls your entity is invalid (because you would have properties that don't agree with each other. Now consider this:
person.ChangeAddress(.......);
all of the calls relating to the behavior of changing an address are now an atomic unit. Your entity is never invalid here.
If you take this idea of modeling behaviors rather than state, then you can reach a model that doesn't allow invalid entities.
For a good discussion on this, check out this infoq interview: http://www.infoq.com/interviews/greg-young-ddd
I like Jimmy Bogard's solution to this problem. He has a post on his blog titled "Entity validation with visitors and extension methods" in which he presents a very elegant approach to entity validation that suggest the implementation of a separate class to store validation code.
public interface IValidator<T>
{
bool IsValid(T entity);
IEnumerable<string> BrokenRules(T entity);
}
public class OrderPersistenceValidator : IValidator<Order>
{
public bool IsValid(Order entity)
{
return BrokenRules(entity).Count() == 0;
}
public IEnumerable<string> BrokenRules(Order entity)
{
if (entity.Id < 0)
yield return "Id cannot be less than 0.";
if (string.IsNullOrEmpty(entity.Customer))
yield return "Must include a customer.";
yield break;
}
}
I usualy use a specification class,
it provides a method (this is C# but you can translate it in any language) :
bool IsVerifiedBy(TEntity candidate)
This method performs a complete check of the candidate and its relations.
You can use arguments in the specification class to make it parametrized, like a check level...
You can also add a method to know why the candidate did not verify the specification :
IEnumerable<string> BrokenRules(TEntity canditate)
You can simply decide to implement the first method like this :
bool IsVerifiedBy(TEntity candidate)
{
return BrokenRules(candidate).IsEmpty();
}
For broken rules, I usualy write an iterator :
IEnumerable<string> BrokenRules(TEntity candidate)
{
if (someComplexCondition)
yield return "Message describing cleary what is wrong...";
if (someOtherCondition)
yield return
string.Format("The amount should not be {0} when the state is {1}",
amount, state);
}
For localization, you should use resources, and why not pass a culture to the BrokenRules method.
I place this classes in the model namespace with names that suggest their use.
Multiple model validation should be going through your aggregate root. If you have to validate across aggregate roots, you probably have a design flaw.
The way I do validation for aggregates is to return a response interface that tells me if validation pass/fail and any messages about why it failed.
You can validate all the sub-models on the aggregate root so they remain consistent.
// Command Response class to return from public methods that change your model
public interface ICommandResponse
{
CommandResult Result { get; }
IEnumerable<string> Messages { get; }
}
// The result options
public enum CommandResult
{
Success = 0,
Fail = 1
}
// My default implementation
public class CommandResponse : ICommandResponse
{
public CommandResponse(CommandResult result)
{
Result = result;
}
public CommandResponse(CommandResult result, params string[] messages) : this(result)
{
Messages = messages;
}
public CommandResponse(CommandResult result, IEnumerable<string> messages) : this(result)
{
Messages = messages;
}
public CommandResult Result { get; private set; }
public IEnumerable<string> Messages { get; private set; }
}
// usage
public class SomeAggregateRoot
{
public string SomeProperty { get; private set; }
public ICommandResponse ChangeSomeProperty(string newProperty)
{
if(newProperty == null)
{
return new CommandResponse(CommandResult.Fail, "Some property cannot be changed to null");
}
SomeProperty = newProperty;
return new CommandResponse(CommandResult.Success);
}
}
This questions a bit old now but in case anyone is interested here's how I implement validation in my service classes.
I have a private Validate method in each of my service classes that takes an entity instance and action being performed, if validation fails a custom exception is thrown with the details of the broken rules.
Example DocumentService with built in validation
public class DocumentService : IDocumentService
{
private IRepository<Document> _documentRepository;
public DocumentService(IRepository<Document> documentRepository)
{
_documentRepository = documentRepository;
}
public void Create(Document document)
{
Validate(document, Action.Create);
document.CreatedDate = DateTime.Now;
_documentRepository.Create(document);
}
public void Update(Document document)
{
Validate(document, Action.Update);
_documentRepository.Update(document);
}
public void Delete(int id)
{
Validate(_documentRepository.GetById(id), Action.Delete);
_documentRepository.Delete(id);
}
public IList<Document> GetAll()
{
return _documentRepository
.GetAll()
.OrderByDescending(x => x.PublishDate)
.ToList();
}
public int GetAllCount()
{
return _documentRepository
.GetAll()
.Count();
}
public Document GetById(int id)
{
return _documentRepository.GetById(id);
}
// validation
private void Validate(Document document, Action action)
{
var brokenRules = new List<string>();
if (action == Action.Create || action == Action.Update)
{
if (string.IsNullOrWhiteSpace(document.Title))
brokenRules.Add("Title is required");
if (document.PublishDate == null)
brokenRules.Add("Publish Date is required");
}
if (brokenRules.Any())
throw new EntityException(string.Join("\r\n", brokenRules));
}
private enum Action
{
Create,
Update,
Delete
}
}
I like this approach because it allows me to put all my core validation logic in one place which keeps things simple.

Resources