Programmatically determine content item type in Orchard without magic strings - asp.net-mvc-3

I am implementing a custom module in Orchard to track the number of views for each content item. In my handler, I am checking to see if the content item's type is the default Orchard "Page" type, but right now it is using the Equals function and magic strings. Here's my code:
public class ContentItemViewRecordHandler : ContentHandler
{
private readonly IOrchardServices services;
private readonly IRepository<ContentItemViewRecord> repository;
public ContentItemViewRecordHandler(IOrchardServices services, IRepository<ContentItemViewRecord> repository)
{
this.services = services;
this.repository = repository;
OnGetDisplayShape<CommonPart>(RecordView);
}
private void RecordView(BuildDisplayContext context, CommonPart part)
{
var contentItemType = context.ContentItem.ContentType;
// THIS IS THE IF STATEMENT IN QUESTION
if (!contentItemType.Equals("Page", StringComparison.CurrentCultureIgnoreCase))
{
return;
}
var contentItemViewRecord = new ContentItemViewRecord
{
ContentItemRecordId = context.ContentItem.Id,
Username = services.WorkContext.CurrentUser.UserName,
HostAddress = services.WorkContext.HttpContext.Request.UserHostAddress,
DateCreated = DateTime.UtcNow
};
repository.Create(contentItemViewRecord);
}
}
Anyone know if there is a way to determine the content item's type without the magic strings?

Content types are not .NET types. They are dynamic entities that only exist at runtime. Strings are therefore perfectly fine to identify them.

Related

Vaadin Select - fields changed inside binder's apply do not write changes to bean from item

Using Vaadin 14.7.0.
Inside a CRUD editor (Enhanced CRUD Editor) I'm building various fields, amongst which I have a Select.
The Select is initialized with a list of options but I'm also trying to change the items from CRUD form edit to CRUD form edit depending on changes from my underlying database so that the user can select new values.
BindingBuilder<Item, SelectOption> bindingBuilder = binder.forField(s);
if (prop.isMandatory()) {
bindingBuilder.asRequired(requiredI18n);
}
bindingBuilder.bind(new ValueProvider<Item, SelectOption>() {
private static final long serialVersionUID = 1L;
#Override
public SelectOption apply(final Item item) {
ListPropertyDefinition lp = ((ListPropertyDefinition)prop);
Serializable currentValue = item.get(lp.getName());
Collection<SelectOption> sOptions = null;
if (lp.getSelectOptions() != null) {
ListDataProvider<SelectOption> ldp = (ListDataProvider)s.getDataProvider();
sOptions = ldp.getItems();
} else {
sOptions = getNewOptions(item, prop.getName());
s.setItems(sOptions);
}
return new SelectOption("N/A", currentValue);
}
}, new Setter<Item, SelectOption>() {
private static final long serialVersionUID = 1L;
#Override
public void accept(final Item bean, final SelectOption fieldvalue) {
bean.set(prop.getName(), fieldvalue != null ? fieldvalue.getValue() : null);
}
});
Now, if the s.setItems(sOptions) branch is being called then the Select field gets populated with the new values sent by the backend but when I'm saving the item the value that I get is null, regardless of what I select in the select field.
This does not happen when I do not change the items in the select field (i.e. if branch).
I did some debugging for comparing 2 select fields - one that changes its values on the fly and one that has values that don't change... from what I could see the field that has values changing on the fly has a null buffered value as seen in the attached image:
vs the field that does not have its values modified in the binder's apply method:
Not sure if what I'm doing is the right way of "refreshing" a select field's values and / or what should I do so that I get the selected value back in the bean on CRUD form save.
I think you are doing things in overly complicated manner. Based on your code I think your principal challenge is how to set empty selection to be "N/A"
For that you simply need to enable empty selection to be allowed. You need to have one placeholder item for empty selection, for which you generate "N/A" as caption. Then you can just do:
Binder<Item> binder = new Binder<>();
Select<SelectOption> select = new Select<>();
...
select.setEmptySelectionAllowed(true);
select.setEmptySelectionCaption("N/A");
binder.forField(s).bind(Item::getProperty,Item::setProperty);
public class SelectOption {
...
}
// Make the item bean also to follow POJO convention
public class Item {
private SelectOption property;
public SelectOption getProperty() {
return property;
}
public void setProperty(SelectOption property) {
this.property = property;
}
}

How to set jqGrid "editoption" with Lib.Web.MVC

I have a bool data value (true / false) that I want to display as Yes / No in the jqGrid cell.
How do I annotate the view model property to do this?
I think the code below may work but I do not know how to add edit options to the property annoation.
editoptions: { value: "false:No;true:Yes" }
Lib.Web.Mvc supports two ways of providing values for select edit/search fields:
Remote - requires a controller action which will return the values via AJAX call (sample can be find in demo project).
Dedicated class - requires creating a class with specific method.
The second way might work in your case. First you need a class to provide your values:
public class YesNoEditOptionsProvider
{
private static readonly IDictionary<string, string> _editOptions = new Dictionary<string, string>() {
{ "false", "No" },
{ "true", "Yes" }
};
public IDicitionary<string, string> GetEditOptions()
{
return _editOptions;
}
}
Now you can apply this class to your propery:
[JqGridColumnEditable(true, typeof(YesNoEditOptionsProvider), "GetEditOptions", EditType = JqGridColumnEditTypes.Select)]
public bool YesNoColumn { get; set; }

How to create dynamic, multiple partial views using repository pattern in MVC

I am trying to have a general home page that depending on the parameter passed to the control, different content (modules) will be displayed.
For example, a user may select Kentucky from the menu and the id for Kentucky is 1. The home controller gets the id (1) and determines the possible modules for that
state (a simple db call.) Perhaps there is an announcements module and a contacts module for the state. An announcements module could have several items but it's only one module. There would be a partial view for each type of module.
Here is the basic setup I have.
public interface IModuleRepository
{
IList<MenuItemModule> GetMenuItemModules(int menuItem);
IList<Announcements> GetAnnouncements(int modID);
IList<News> GetNews(int modID);
IList<Contacts> GetContacts(int modID);
}
//business object
public class MenuItemModule
{
private int _MenuItemID;
private int _ModuleID;
private int _ModuleDefID;
private string _Src;
private int _ModuleOrder;
//get, set properties for these...
}
//announcements entity
public class Announcements
{
private int _ID = -1;
private int _MenuItemID = -1;
private int _ModuleID = -1;
private string _Description = string.Empty;
//get set props ...
}
In my home controller...
public class HomeController : Controller
{
private IModuleRepository modRepository;
public HomeController(IModuleRepository modRepository)
{
this.modRepository = modRepository;
}
public ViewResult Item(string ItemID)
{
//returns a list of menuitemmodules for the page. This gives me the Src or name of each
//module on the page, i.e. Announcements, news, contacts, etc.
var modules = modRepository.GetMenuItemModules(Convert.ToInt32(ItemID));
return View(modules);
}
}
I have tried several different models to return but I always run up against some contstraint. If I pass the menuitemmodules to my Item.aspx, then I can do something like this:
foreach (var mod in Model)
{
Html.RenderPartial(mod.Src, a); //needs an announcement object though
}
That makes it somewhat dynamic because I have the Src which would basically be something like "Announcements" and I can just create an announcements.ascx partial to process the module. But I have found it difficult to pass my menuitemmodule and an announcements entity as well.
I have also messed around with passing a more complex object and then testing every Src that comes through with an If statement. This would make scaling difficult in the future as I increase the number of possible modules in the app.
How can I solve my problem? I hope I have provided enough info. I like the basic idea here - http://www.mikesdotnetting.com/Article/105/ASP.NET-MVC-Partial-Views-and-Strongly-Typed-Custom-ViewModels but that seems to only work for static modules on a page.
I did try a composite view model called ModuleViewModel. Here is that attempt:
public class ModuleViewModel
{
public IList<Announcements> announcements { get; set; }
public IList<MenuItemModule> mods { get; set; }
}
If I pass that model to the Item.aspx I can do something like this (but I must be doing something wrong because something doesn't look right.)
foreach (var mod in Model)
{
if (mod.announcements.Count > 0)
{
Html.RenderPartial("Announcements", mod.announcements);
}
}
Once again, scalability is going to haunt me. I would like to have something like this on item page:
foreach (var mod in Model)
{
Html.RenderPartial(mod.Src, mod);
}
That would the correct partial view and pass it the correct model.
Create Module classes that derive from a common Module base class:
public class AnnouncementsModule : Module
{
}
public class ContactsModule : Module
{
}
In controller:
Create your various modules and put them into your overall view module (here it has a property called Modules that is an array of Module:
var viewModel = new ComplexViewModel
{
Modules = new []
{
new ContactsModule(),
new AnnouncementsModule()
}
};
return View(viewModule);
In view:
#Html.DisplayFor(x => x.Modules);
Create the partial views for each Type of Module in the appropriate 'Shared` folder. (Run it without creating them and it will show you an exception with the locations where it's looking for them).
After messing around with this for over a week, I finally managed to figure out how MVC can do what I want dynamically. I decided to post my solution for others that are new to MVC. Hopefully, the following will clear up the misunderstandings I had (although, at this point in my understanding of MVC, I cannot say this is the best approach.)
I will include the previous code snips and modifications for clarity:
public interface IModuleRepository
{
IList<MenuItemModule> GetMenuItemModules(int menuItem);
IList<Announcements> GetAnnouncements(int modID);
IList<News> GetNews(int modID);
IList<Contacts> GetContacts(int modID);
}
//business object
public class MenuItemModule
{
private int _MenuItemID;
private int _ModuleID;
private int _ModuleDefID;
private string _Src;
private int _ModuleOrder;
//get, set properties for these...
}
//announcements entity
public class Announcements : MenuItemModule
{
private int _ID = -1;
private string _Description = string.Empty;
//get set props ...
}
I also added another class:
public class AnnouncementModule : MenuItemModule
{
private IList<Announcements> _Announcements;
//get set prop
}
...and I created a model for the view
public class HomeItemViewModel
{
public MenuItemModule[] MenuItemModules { get; set; } //collection of menuitemmodules
}
In my home controller...
var menuItemModules = modRepository.GetMenuItemModules(ItemID);
if (menuItemModules.Count > 0)
{
AnnouncementModule aMod;
MenuItemModule[] mods = new MenuItemModule[menuItemModules.Count()];
int i = 0;
//loop through each MenuItemModule assign to the appropriate model
foreach (MenuItemModule mod in menuItemModules)
{
if (mod.Src == "Announcements")
{
aMod = new AnnouncementModule();
aMod.Announcements = modRepository.GetAnnouncements(mod.ModuleID);
//now add this to the menuitemmodule collection
mods[i] = aMod;
}
if (mod.Src == "Contacts")
{
//...
}
i++;
}
}
var viewModel = new HomeItemViewModel
{
MenuItemModules = mods
};
return View(viewModel);
Then I used the suggestion to use DisplayFor in the view. The view is strongly typed to HomeItemViewModel.
<%: Html.DisplayFor(m => m.MenuItemModules) %>
This iterates through the collection and based on the type, it will call that template. In this example, it calls AnnouncementModule.ascx which is strongly typed to AnnouncementModule.
foreach (var a in Model.Announcements)
{
//a.Description will give us the description of the announcement
}
I realize there are slicker ways to code the controller, and I plan on refactoring, but this skeleton should provide the basics to solve the question I posted.

Is there a way to invalidate the ASP.NET MVC 3 ActionCache

I have an ASP.NET MVC 3 application that uses custom attributes to create select controls for model properties that can be populated from external data sources at runtime. The issue is that my EditorTemplate output appear to be cached at the application level, so my drop down lists are not updated when their data source changes until the Application Pool is recycled.
I also have output the contents of the MVC 3 ActionCache that is bound to the ViewContext.HttpContext object as shown in the MVC 3 source code in System.Web.Mvc.Html.TemplateHelpers.cs:95.
Action Cache GUID: adf284af-01f1-46c8-ba15-ca2387aaa8c4:
Action Cache Collection Type: System.Collections.Generic.Dictionary``2[System.String,System.Web.Mvc.Html.TemplateHelpers+ActionCacheItem]
Action Cache Dictionary Keys: EditorTemplates/Select
So it appears that the Select editor template is definitely being cached, which would result in the TemplateHelper.ExecuteTemplate method to always return the cached value instead of calling ViewEngineResult.View.Render a second time.
Is there any way to clear the MVC ActionCache or otherwise force the Razor view engine to always re-render certain templates?
For reference, Here are the relevant framework components:
public interface ISelectProvider
{
IEnumerable<SelectListItem> GetSelectList();
}
public class SelectAttribute : Attribute, IMetadataAware
{
private readonly ISelectProvider _provider;
public SelectAttribute(Type type)
{
_provider = DependencyResolver.Current.GetService(type) as ISelectProvider;
}
public void OnMetadataCreated(ModelMetadata modelMetadata)
{
modelMetadata.TemplateHint = "Select";
modelMetadata.AdditionalValues.Add("SelectListItems", SelectList);
}
public IEnumerable<SelectListItem> SelectList
{
get
{
return _provider.GetSelectList();
}
}
}
Next, there is a custom editor template in ~\Views\Shared\EditorTemplates\Select.cshtml.
#model object
#{
var selectList = (IEnumerable<SelectListItem>)ViewData.ModelMetadata.AdditionalValues["SelectListItems"];
foreach (var item in selectList)
{
item.Selected = (item != null && Model != null && item.Value.ToString() == Model.ToString());
}
}
#Html.DropDownListFor(s => s, selectList)
Finally, I have a view model, select provider class and a simple view.
/** Providers/MySelectProvider.cs **/
public class MySelectProvider : ISelectProvider
{
public IEnumerable<SelectListItem> GetSelectList()
{
foreach (var item in System.IO.File.ReadAllLines(#"C:\Test.txt"))
{
yield return new SelectListItem() { Text = item, Value = item };
}
}
}
/** Models/ViewModel.cs **/
public class ViewModel
{
[Select(typeof(MySelectProvider))]
public string MyProperty { get; set; }
}
/** Views/Controller/MyView.cshtml **/
#model ViewModel
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="Submit" />
}
** EDIT **
Based on suggestions in the comment, I started to look more closely at the ObjectContext lifecycle. While there were some minor issues, the issue appears to be isolated to an odd behavior involving a callback within a LINQ expression in the SelectProvider implementation.
Here is the relevant code.
public abstract class SelectProvider<R, T> : ISelectProvider
where R : class, IQueryableRepository<T>
{
protected readonly R repository;
public SelectProvider(R repository)
{
this.repository = repository;
}
public virtual IEnumerable<SelectListItem> GetSelectList(Func<T, SelectListItem> func, Func<T, bool> predicate)
{
var ret = new List<SelectListItem>();
foreach (T entity in repository.Table.Where(predicate).ToList())
{
ret.Add(func(entity));
}
return ret;
}
public abstract IEnumerable<SelectListItem> GetSelectList();
}
public class PrinterSelectProvider : SelectProvider<IMyRepository, MyEntityItem>
{
public PrinterSelectProvider()
: base(DependencyResolver.Current.GetService<IMyRepository>())
{
}
public override IEnumerable<SelectListItem> GetSelectList()
{
// Create a sorted list of items (this returns stale data)
var allItems = GetSelectList(
x => new SelectListItem()
{
Text = x.DisplayName,
Value = x.Id.ToString()
},
x => x.Enabled
).OrderBy(x => x.Text);
// Do the same query, but without the callback
var otherItems = repository.Table.Where(x => x.Enabled).ToList().Select(x => new SelectListItem()
{
Text = x.DisplayName,
Value = x.Id.ToString()
}).OrderBy(x => x.Text);
System.Diagnostics.Trace.WriteLine(string.Format("Query 1: {0} items", allItems.Count()));
System.Diagnostics.Trace.WriteLine(string.Format("Query 2: {0} items", otherItems.Count()));
return allItems;
}
}
And, the captured output from the System.Diagnostics.Trace is
Query 1: 2 items
Query 2: 3 items
I'm not sure what could be going wrong here. I considered that the Select may need an Expressions, but I just double-checked and the LINQ Select method only takes Func objects.
Any additional suggetions?
Problem Solved!
I finally had a chance to re-visit this issue. The root cause had nothing to do with LINQ, the ActionCache, or the ObjectContext, rather it was related to when attribute constructors are called.
As shown, my custom SelectAttribute class calls DependencyResolver.Current.GetService in its constructor to create an instance of the ISelectProvider class. However, the ASP.NET MVC framework scans the assemblies for custom metadata attributes once and keeps a reference to them in the application scope. As explained in the linked question, accessing a Attribute triggers its constructor.
So, the constructor was run only once, rather than on each request, as I had assumed. This meant that there was actually only one, cached instance of the PrinterSelectProvider class instantiated that was shared across all requests.
I solved the problem by changing the SelectAttribute class like this:
public class SelectAttribute : Attribute, IMetadataAware
{
private readonly Type type;
public SelectAttribute(Type type)
{
this.type = type;
}
public void OnMetadataCreated(ModelMetadata metadata)
{
// Instantiate the select provider on-demand
ISelectProvider provider = DependencyResolver.Current.GetService(type) as ISelectProvider;
modelMetadata.TemplateHint = "Select";
modelMetadata.AdditionalValues.Add("SelectListItems", provider.GetSelectList());
}
}
Tricky problem indeed!

LINQ-To-Sharepoint Multiple content types for a single list

I'm using SPMetal in order to generate entity classes for my sharepoint site and I'm not exactly sure what the best practice is to use when there are multiple content types for a single list. For instance I have a task list that contains 2 content types and I'm defining them via the config file for SPMetal. Here is my definition...
<List Member="Tasks" Name="Tasks">
<ContentType Class="LegalReview" Name="LegalReviewContent"/>
<ContentType Class="Approval" Name="ApprovalContent"/>
</List>
This seems to work pretty well in that the generated objects do inherit from WorkflowTask but the generated type for the data context is a List of WorkflowTask. So when I do a query I get back a WorkflowTask object instead of a LegalReview or Approval object. How do I make it return an object of the correct type?
[Microsoft.SharePoint.Linq.ListAttribute(Name="Tasks")]
public Microsoft.SharePoint.Linq.EntityList<WorkflowTask> Tasks {
get {
return this.GetList<WorkflowTask>("Tasks");
}
}
UPDATE
Thanks for getting back to me. I'm not sure how I recreate the type based on the SPListItem and would appreciate any feedback.
ContractManagementDataContext context = new ContractManagementDataContext(_url);
WorkflowTask task = context.Tasks.FirstOrDefault(t => t.Id ==5);
Approval a = new Approval(task.item);
public partial class Approval{
public Approval(SPListItem item){
//Set all properties here for workflowtask and approval type?
//Wouldn't there be issues since it isn't attached to the datacontext?
}
public String SomeProperty{
get{ //get from list item};
set{ //set to list item};
}
Linq2SharePoint will always return an object of the first common base ContentType for all the ContentTypes in the list. This is not only because a base type of some description must be used to combine the different ContentTypes in code but also it will then only map the fields that should definitely exist on all ContentTypes in the list. It is however possible to get access to the underlying SPListItem returned by L2SP and thus from that determine the ContentType and down cast the item.
As part of a custom repository layer that is generated from T4 templates we have a partial addition to the Item class generated by SPMetal which implements ICustomMapping to get the data not usually available on the L2SP entities. A simplified version is below which just gets the ContentType and ModifiedDate to show the methodology; though the full class we use also maps Modified By, Created Date/By, Attachments, Version, Path etc, the principle is the same for all.
public partial class Item : ICustomMapping
{
private SPListItem _SPListItem;
public SPListItem SPListItem
{
get { return _SPListItem; }
set { _SPListItem = value; }
}
public string ContentTypeId { get; internal set; }
public DateTime Modified { get; internal set; }
public virtual void MapFrom(object listItem)
{
SPListItem item = (SPListItem)listItem;
this.SPListItem = item;
this.ContentTypeId = item.ContentTypeId.ToString();
this.Modified = (DateTime)item["Modified"];
}
public virtual void MapTo(object listItem)
{
SPListItem item = (SPListItem)listItem;
item["Modified"] = this.Modified == DateTime.MinValue ? this.Modified = DateTime.Now : this.Modified;
}
public virtual void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
{
SPListItem originalItem = (SPListItem)originalListItem;
SPListItem databaseItem = (SPListItem)databaseObject;
DateTime originalModifiedValue = (DateTime)originalItem["Modified"];
DateTime dbModifiedValue = (DateTime)databaseItem["Modified"];
string originalContentTypeIdValue = originalItem.ContentTypeId.ToString();
string dbContentTypeIdValue = databaseItem.ContentTypeId.ToString();
switch(mode)
{
case RefreshMode.OverwriteCurrentValues:
this.Modified = dbModifiedValue;
this.ContentTypeId = dbContentTypeIdValue;
break;
case RefreshMode.KeepCurrentValues:
databaseItem["Modified"] = this.Modified;
break;
case RefreshMode.KeepChanges:
if (this.Modified != originalModifiedValue)
{
databaseItem["Modified"] = this.Modified;
}
else if (this.Modified == originalModifiedValue && this.Modified != dbModifiedValue)
{
this.Modified = dbModifiedValue;
}
if (this.ContentTypeId != originalContentTypeIdValue)
{
throw new InvalidOperationException("You cannot change the ContentTypeId directly");
}
else if (this.ContentTypeId == originalContentTypeIdValue && this.ContentTypeId != dbContentTypeIdValue)
{
this.ContentTypeId = dbContentTypeIdValue;
}
break;
}
}
}
Once you have the ContentType and the underlying SPListItem available on your L2SP entity it is simply a matter of writing a method which returns an instance of the derived ContentType entity from a combination of the values of the base type and the extra data for the missing fields from the SPListItem.
UPDATE: I don't actually have an example converter class as we don't use the above mapping extension to Item in this way. However I could imagine something like this would work:
public static class EntityConverter
{
public static Approval ToApproval(WorkflowTask wft)
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = wft.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = wft.SPListItem["field-name"];
return a;
}
}
Or you could put a method on a partial instance of WorkflowTask to return an Approval object.
public partial class WorkflowTask
{
public Approval ToApproval()
{
Approval a = new Approval();
a.SomePropertyOnWorkflowTask = this.SomePropertyOnWorkflowTask;
a.SomePropertyOnApproval = this.SPListItem["field-name"];
return a;
}
public LegalReview ToLegalReview()
{
// Create and return LegalReview as for Approval
}
}
In either situation you would need to determine the method to call to get the derived type from the ContentTypeId property of the WorkflowTask. This is the sort of code I would normally want to generate in one form or another as it will be pretty repetitive but that is a bit off-topic.

Resources