I am writing custom property for umbraco that counts hits on document.
Is there any way how can I hook up my logic to page when document is loading?
Example:
I have structure:
Home
About me
Both have my custom Property.
I would like to record when they have been loaded.
Note: I do not want to use any javascript, or actions on page. I would like to have it as one custom DLL.
Solution that I have chosen is:
use IHttpModule as umbraco does not have any event that can be used for this.
As to configuration:
I have decided dynamicaly hook up my module after application start.
Thnx all for considering my question.
One way to do it would to have a separate db table specifically for page hits, e.g. [PageHit] which had two columns [NodeId] and [HitCount].
A hit on a page either created a new record if no hits had previously been recorded, or incremented the count on an existing record.
You would need to ensure that the hit was recorded per page, so recording it in the your base Masterpage or in a base Controller would be my approach.
You could then have a property based upon a custom datatype that looked up the hit count for the specific node that the property existed upon. The custom datatype could be a usercontrol, and since in the Umbraco backoffice when you load a node in the content editor the node's ID is in the query of the URL, you can access this in the usercontrol to query the database.
Using a separate table allows you to separate things into a separate DLL too as you would not need to be reliant on the Umbraco API as you could use an ORM like PetaPoco or basic ADO.
I certainly wouldn't write directly back to the node itself as this would cause an unnecessary load on the database, causing latency on the site's page loads and generally slow things down.
If you want to keep this data inside the Umbraco backend, you'd need to create an usercontrol and embed that control in the masterpage (top masterpage);
In the usercontrol add these usings:
using umbraco.cms.businesslogic.web;
using umbraco.BusinessLogic;
using umbraco.presentation.nodeFactory;
And a method similar to this:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack) // don't want to count postbacks
{
Document nodeCount = new Document(Node.GetCurrent().Id);
int Count = 0;
try
{
Count = Convert.ToInt32(nodeCount.getProperty("hitCount").Value);
}
catch
{
Count = 0; // value == null, not set yet
}
nodeCount.getProperty("hitCount").Value = Count + 1;
nodeCount.Save();
nodeCount.Publish(new User(0));
umbraco.library.UpdateDocumentCache(nodeCount.Id);
}
PS this is written without testing.. just of the top of my head
Related
I am using Selenium Page factory and I want to make certain extensions to it, but can't as they are sealed, so I want to write my own custom logic.
Question :
How does PageFactory.InitElements work so that all the properties loads their values when they are getting used and not when this method is called..
So, To Explain it with an example
//// Button on page load
[FindsBy(How = How.CssSelector, Using = "#lst-ib")]
public IWebElement Btn;
//// Button redirecting to Page 2
[FindsBy(How = How.CssSelector, Using = "#lst-ib")]
public IWebElement LinkBtn;
////Button on second page
[FindsBy(How=How.CssSelector, Using = "#rso > div:nth-child(1) > div > div:nth-child(1) > div > div > h3 > a")]
public IWebElement NewBtn;
So, the beauty of their page factory is that all the elements are loaded but they convert to webelements when they are in use, beacause if all the properties are assigned values on initilisation, NewBtn Property would always fail, as it is on page 2 .
So, what concept they might be using of initializing properties so they are assigned on usage and not at the run time , any dummy code would be great and appreciated to understand
Thanks in Advance
I have got absolutely no experience in C# but I checked the source code of the C# selenium implementation and seems pretty much identical to the Java code.
PageFactory.cs - This class provides the overall framework of how the elements are initialized.
The initElements() gets all the fields in the pageobject. For each field gets the annotation on them. Then it creates a Proxy for each field.
Then it stores the actual locator to be used. The actual call to findElement or findElements is inside the Invoke method of the proxy.
This is all handled by two classes - DefaultPageObjectMemberDecorator.cs and DefaultElementLocator.cs. There are two proxies which handle invocations - WebElementProxy.cs and WebElementListProxy.cs
So the custom logic that you want to write could be done by creating new classes which implement the appropriate interfaces. Then pass these new classes into the appropriate initElement() method of PageFactory class.
For a detailed understanding look at the Decorate() method of DefaultPageObjectMemberDecorator.cs class.
When creating the page object, the "page object logic" (IMO, from my experience and what I have understood so far from c# and selenium) it expects all the elements mentioned in an element map to exist in the DOM.
If your NewBtn exists in the dome but is hidden (and becomes visible when navigating to second page, basically when the page does not get refreshed and there is some ajax to it), then it stands to reason that the element map won't have any problem handling the element. You just have to assure that the element is visible with an IF statement or add a wait (to see if you are indeed to the second page).
If the page gets refreshed when navigating to the second page, then simply re-initialize your page object with new MyPageObject() so that all elements get mapped again, to avoid StaleElement and/or ElementNotFound exceptions etc.
Here is a little background on the specifications of my project:
We use Specflow and Microsoft CodedUI Framework for UI Automation
I have built a PageFactory that combines three Abstract Base Classes : BasePage, BaseMap, and BaseValidator that all Maps, Pages, and Validators inherit
Our Application that we are automating has numerous workflows that make defined HTML Controls have different InnerText Values (HTMLComboBoxes for example)
Everything is and needs to be abstracted from the actual Specflow Test Code in the Page Object Pattern, no unique code can exist within a Specflow Step
In my Maps I have certain controls like a combobox that has an InnerText change if a certain workflow is selected. I need to build assertion and verification statements to make sure the InnerText is correct for the workflow that is selected. This is not a problem. However, I do not want to just define a new variable for every InnerText change(There are A LOT).
Is there any way I can account for the InnerText variations in the Page Object Pattern and not have to code a new variable for every single one?
Here is an example of a Map Entry:
public HtmlComboBox NextActionControlDropDownList()
{
var NextActionControlDropDownList = new PropertyExpressionCollection {
new PropertyExpression(HtmlComboBox.PropertyNames.Id, "MEDCHARTContent_EmmpsContent_nextActionControl_ActionDropDownList", PropertyExpressionOperator.EqualTo)
};
return Window.Find<HtmlComboBox>(NextActionControlDropDownList);
}
This is the Base Control definition. It can also be this:
public HtmlComboBox NextActionControlARFormalComplReview()
{
var NextActionControlARFormalComplReview = new PropertyExpressionCollection {
new PropertyExpression(HtmlComboBox.PropertyNames.Id, "MEDCHARTContent_EmmpsContent_nextActionControl_ActionDropDownList", PropertyExpressionOperator.EqualTo),
new PropertyExpression(HtmlComboBox.PropertyNames.InnerText, "--Select Action-- Return to USARC ", PropertyExpressionOperator.EqualTo)
};
return Window.Find<HtmlComboBox>(NextActionControlARFormalComplReview);
}
My thoughts so far were to maybe make another map and inherit it? But that wouldn't solve my initial problem of too many variables for a single control. I don't see how If statements would help either because it needs to be defined for the framework to find the control. Maybe I could store the differing values in a collection of sorts and have a parameter key value to access them... but that seems like I would run into a lot of issues.
If you try and see the methods under PropertyExpressionOperator you would see something called Contains.
new PropertyExpression(HtmlComboBox.PropertyNames.InnerText, "--Select Action--", PropertyExpressionOperator.Contains)
I need to hook into the Creating event in Orchard CMS and use the Title of the content that's being created (Along with some other properties) to create an item in a 3rd party system and return the ID of that to set in the content type in Orchard.
I have a custom content type, but when I try to hook into the events (as explained in the docs here and also by looking at code on the built in Orchard Core content parts) all properties are null.
Are they just lazy loaded? Is there a way to populate them? Overriding any of the shape methods (GetItemMetadata / BuildDisplayShape / BuildEditorShape / UpdateEditorShape) doesn't seem right as this should only fire when initially creating the content type.
My code is:
public MyContentPartHandler(IRepository<MyContentPartRecord> repository, IOrchardServices orchardServices, Lazy<IMyContentPartService> myContentPartService) {
_orchardServices = orchardServices;
_myContentPartService = myContentPartService;
Filters.Add(StorageFilter.For(repository));
OnCreating<MyContentPart>(CreateTPItemAndAssignIdentity);
}
protected void CreateTPItemAndAssignIdentity(CreateContentContext context, MyContentPart part)
{
//create item in 3rd party system
var item = _myContentPartService.Value.CreateNewItem(part.Title, part.Path);
part.ExternalIdentity = item.FriendlyId;
}
The CreateNewItem() method fails as the part.Title and part.Path are null. Do I need to try and get at the Record? (I thought not, as the Orchard CMS record has not been created at that point)
UPDATE - I have also tried to use the OnCreated event instead, but I run into the same problem with the properties not being populated. I place a breakpoint in the code, and noticed that when the OnCreated breakpoint was hit - the data did not actually exist in the database at that time.
OnCreating/OnCreated events are fired when an item object is constructed, not populated with data.
If you want to be sure that data is already there, use either OnPublished event (fired after item has been updated) or OnLoading/OnLoaded events if you want to run some code after an item has been fully loaded from database.
This description of certain events might also be helpful.
When navigating to another page how can i make my list of object available to another page.
for example in my mainpage.xaml
var data2 = from query in document.Descendants("weather")
select new Forecast
{
date = (string)query.Element("date"),
tempMaxC = (string)query.Element("tempMaxC"),
tempMinC = (string)query.Element("tempMinC"),
weatherIconUrl = (string)query.Element("weatherIconUrl"),
};
forecasts = data2.ToList<Forecast>();
....
NavigationService.Navigate(new Uri("/WeatherInfoPage.xaml", UriKind.Relative));
and then in my other class, i want to make it available so that i can use it like this
private void AddPageItem(List<Forecast> forecasts)
{
..
}
this may help you
//using PhoneApplicationService.Current.State to store the list//
PhoneApplicationService.Current.State["yourparam"] = lstpro.SelectedItems;
NavigationService.Navigate(new Uri("/res.xaml", UriKind.Relative))
And in The Second Page
private IList iList1;
//In The onNavigatedTo Event assign the stored list to the variable//
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
var i= PhoneApplicationService.Current.State["yourparam"];
//convert object to list//
iList1 = (IList) i ;
lstpro.ItemsSource = iList1;
}
You could use a global variable which you place in App.xaml.cs:
//In App.xaml.cs
public static List<Forecast> SelectedForecasts = null;
The variable will be available in the whole application.
In general I use an additional class implemented with a Singleton pattern. In this class I store every data that needs to be exchanged between pages. Basically it's the same approach as Jon mentioned, but I like to keep my App.xaml.cs clean. So you can access your data from everywhere in your application.
There are several ways to accomplish this but none of them is truely elegant. The main issue being to handle tombstoning scenarios : To be restored entirely, your page has to be able to retrieve its input list even if the App has just been re-activated (and all previous states coming from previous navigation erased...)
You can serialize your list with your own custom format and add it to the end of your Uri. The downside being that you have to parse and deserialize the Uri by yourself (Maybe this way looks more natural for simple and small amount of data like an id for instance).
To navigate to the next page :
NavigationService.Navigate(new Uri("/WeatherInfoPage.xaml?data1;data2;serializeddata", UriKind.Relative));
To retrieve data in the next page, you would have to parse and to deserialize it from the current Uri :
Uri currentUri = ((App)Application.Current).RootFrame.CurrentSource;
You can also store your list into a dedicated variable located in AppSettings or PhoneApplicationService.Current.State before navigating and retrieving it in the new page. Perharps it could be interesting to create a dedicated class to do this job. It could be a centralized place to push and retrieve parameters, to ensure parameters uniqueness, to manage parameters lifetime and so on... It could also be applied to the first proposal.
Finally, if your list is persisted somewhere (a file, embedded database), you can just pass an id to the next page (using previous proposals) and retrieve the corresponding list by querying it from your persisted source.
In both cases, you will be able to pass your list to the next page and handle tombstoning scenarios.
Context:
I've often been in situations where our ASP.NET pages would have to show data to the user on a GridView, let him change it as he pleases (Textbox on cells) and only save it to the database when he actually hits the "Save Button". This data is usually a virtual state of the information on the page, meaning that the user can change everything without really saving it until he hits the "Save Button".
In those cases, there's always list of data that needs to be persisted across ASP.NET Postbacks. This data could be an instance of a DataTable or just some List<Someclass>.
I often see people implementing this and persisting the data on Session. On that cases i also usually see problems when it comes to some user navigating with multiple tabs open, some times on the same page. Where the data of two different tabs would get merged and cause problems of information being scrambled.
Example of how Session is often used:
private List<SomeClass> DataList
{
get
{
return Session["SomeKey"] as List<SomeClass>;
}
set
{
Session["SomeKey"] = value;
}
}
People often tries to solve it by doing something like this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DataList = null
}
else
{
FillGridView(DataList);
}
}
But what about when two tabs are already loaded and the user is changing the GridView values and for some weird reason he tries to save the data by hitting the Save button on the other page? I personally dislike this option.
Other ways to do this would be to put the data on ViewState. However, when it comes to persisting substantially big lists, it could impact the page heavily when it's stored on the page (HiddenField).
But, what's the best way to make that work? Once, i thought in using Session together with ViewState where the ViewState would hold an unique identifier which would index the Session saved data. That would prevent sharing the data between tabs on the browser:
private List<SomeClass> DataList
{
get
{
if (ViewState["SomeKey"] == null)
{
ViewState["SomeKey"] = Guid.NewGuid().ToString();
}
return Session[ViewState["SomeKey"].ToString()] as List<SomeClass>;
}
set {
if (ViewState["SomeKey"] == null)
{
ViewState["SomeKey"] = Guid.NewGuid().ToString();
}
Session[ViewState["SomeKey"].ToString()] = value;
}
}
On the other hand it would store a new list of data to the Session every time the user enters the page. Which would impact the server memory. Maybe they could be erased in some way.
Question:
What would be the best way of persisting that kind of data across Postbacks, considering the contexts of multiple tabs on the browser, with the less cost to the server and to the maintenance coding team?
Update:
As #nunespascal nicely posted, one option would be to store the ViewState in the Session using the SessionPageStatePersister. But unfortunately that's not an option on my case. And yet it is not very different from my last example, saving the data on the Session indexed by an UniqueId stored on the ViewState.
Would there be any other options?
There is a simple solution to that problem. Store the ViewState in the Session.
For that you need to use the SessionPageStatePersister
Refer: Page State Persister
All you need to do is override the PageStatePersister and make it use SessionPageStatePersister instead of the default HiddenFieldPageStatePersister
protected override PageStatePersister PageStatePersister
{
get
{
return new SessionPageStatePersister(this);
}
}
This even saves you the headache of maintaining a unique key. A hidden field will be used automatically to keep a unique key per instance of the page.
I've come across a similar situation. The idea is if you allow long sessions for each user to change the grid view, this means you'll also have a concurrency problem because eventually you will accept only one last set of modifications to your data.
So, my solution was, to allow changes on the database but make sure all the users see the same state via SignalR.
Now, the concurrency problem has disappeared but you still need to make the changes on the fly. You might not want to save the changes after all. I've solved this problem by applying the command design pattern. Now any set of changes can either be approved or discarded. Whenever you check the index you will see the last approved gridview. Go to update page and you see the live-updated gridview. Also, go to revisions to see old approved gridview -another advantages of command design pattern-.