How to set Media Url of MediaPart? Orchard MediaLibraryPickerField MediaParts is null - orchardcms

I am trying to automatically assign the MediaPart.MediaUrl to a custom image upon published. But..
MediaPickerField MediaParts is always null.
OnPublished<BlogPostPart>((context, part) =>
{
ContentPart cp = part.ContentItem.Parts.Skip(13).First();
MediaLibraryPickerField field = cp.Fields.FirstOrDefault() as MediaLibraryPickerField;
if (field != null) {
// this part throws an object null reference exception since fields.MediaParts is null
foreach(Orchard.MediaLibrary.Models.MediaPart mp in field.MediaParts){
}
}
});
How to programically set the MediaUrl from the code on the event on publish? I am using Orchard 1.8

MediaParts is implemented as a lazy field so that the value isn't fetched unnecessarily. The loader for the lazy field is set-up by the handler for the field, during the Loaded event.
A content item that has just been created has gone through Creating, Created, and Publishing before it goes into Published. That means that the media library picker fields's media part collection is not available at this point.
What is it exactly that you're trying to do?
Also, you should never rely on the order or number of parts on a content item, so that Skip(13) is not the right way of getting to the part you want.

Related

Alternate shape for EditorTemplate of Field is not being recognized

I need an alternate for the EditorTemplate of an Enumerator Field that's used when the Field has a particular name (PublishingMethod).
Based on the docs, I created a view with the pattern [ShapeType__FieldName] in the same folder as the original shape:
This is not working and still uses the original. I've thought of changing the Editor method in the Driver, but I think that defeats the purpose of alternates, which is that Orchard automatically detects the correct shape as I understand from the docs:
The Orchard framework automatically creates many alternates that you can use in your application. However, you can create templates for these alternate shapes.
Note: I can't use the Shape Tracing module, it never worked even with a clean Orchard install.
The editors in Orchard work different to how Display works. I guess it is so you get a MVC-style experience. Basically, the actual shape returned is of type EditorTemplate, which then binds your model and prefix then renders a partial view with the template name you gave it. What this means is alternates wont work as expected, or as the docs state. The alternates for your field name are actually added to the EditorTemplate shape. So what you can do is add a view called EditorTemplate-PublishingMethod.cshtml with contents like:
#{
var m = (Orchard.Fields.Fields.EnumerationField)Model.Model;
}
#Html.Partial("PublishingMethodEditor", m, new ViewDataDictionary {
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = Model.Prefix }
})
Then add another view called PublishingMethodEditor.cshtml with the overrides you want for your editor. All these views should go in the root of your Views folder.
Another approach would be to implement the IShapeTableProvider interface and adjust the TemplateName property on a certain condition but, meh, that requires code...
Edit 1
If you have that field name on other content types that you don't want to override you can use the override EditorTemplate-ContentTypeName-PublishingMethod.cshtml

OrchardCMS: How to access Content Menu Item boolean field in cshtml view

In orchard, I've added a boolean field called "IsDone" to the built in Content Menu Item content part via that Admin interface. I've then picked an item in Navigation and set the option to "yes" for the corresponding field i added.
In my custom theme, I've copied over MenuItem.cshtml.
How would I get the value of my custom "IsDone" field here?
I've tried something like
dynamic item = Model.ContentItem;
var myValue = item.MenuItem.IsDone.Value;
but I'm pretty sure my syntax is incorrect (because i get null binding errors at runtime).
thanks in advance!
First i suggest you use the shape alternate MenuItemLink-ContentMenuItem.cshtml instead of MenuItem.cshtml to target the content menu item directly.
Secondly, the field is attached to the ContentPart of the menu item. The following code retrieves the boolean field from this content part:
#using Orchard.ContentManagement;
#using System.Linq;
#{
Orchard.ContentManagement.ContentItem lContentItem = Model.Content.ContentItem;
var lBooleanField = lContentItem
.Parts
.Where(p => p.PartDefinition.Name == "ContentMenuItem") // *1
.SelectMany(p => p.Fields.Where(f => f.Name == "IsDone"))
.FirstOrDefault() as Orchard.Fields.Fields.BooleanField;
if (lBooleanField != null)
{
bool? v = lBooleanField.Value;
if (v.HasValue)
{
if (v.Value)
{
#("done")
}
else
{
#("not done")
}
}
else
{
#("not done")
}
}
}
*1
Sadly you cannot simply write lContentItem.As<Orchard.ContentManagement.ContentPart>() here as the first part in the part list is derived from this type, thus you would receive the wrong part.
While #ViRuSTriNiTy's answer is probably correct, it doesn't take advantage of the power of the dynamic objects that Orchard provides.
This is working for me but is a much shorter version:
#Model.Text
#{
bool? IsDone = Model.Content.ContentMenuItem.IsDone.Value;
var IsItDoneThough = (IsDone.HasValue ? IsDone.Value : false);
}
<p>Is it done? #IsItDoneThough</p>
You can see that in the first line I pull in the IsDone field using the dynamic nature of the Model.
For some reason (I'm sure there is a good one somewhere) the BooleanField uses a bool? as its backing value. This means that if you create the new menu item and just leave the checkbox blank it will be null when you query it. After you have saved it as checked it will be true and then if you go back and uncheck it then it will have the value false.
The second line that I've provided IsItDoneThough checks if it has a value yet. If it does then it uses that, otherwise it assumes it to be false.
Shape Alternate
#ViRuSTriNiTy's other advice, to change it to use the MenuItemLink-ContentMenuItem.cshtml instead of MenuItem.cshtml is also important.
The field doesn't exist on other menu items so it will crash if you try to access it. Just rename the .cshtml file to fix this.
Dynamic Model
Just to wrap this up with a little bit of insight as to how I got there (I'm still learning this as well) the way I figured it out is as follows:
.Content is a way of casting the current content item to dynamic, so you can use the dynamic advantages with the rest of line;
When you add the field in the admin panel it looks like it should be right there on the ContentItem, however it actually creates an invisible ContentPart to contain them and calls it whatever the ContentItem's type is.
So if you had added this field to a Page content type you would have used Model.Content.Page.IsDone.Value. If you had made a new content type called banana it would be Model.Content.Banana.IsDone.Value, etc.
Once you are inside the "invisible" part which holds the fields you can finally get at IsDone. This won't give you the actual value yet though. Each Field has its own properties which you can look up in the source code. the IsDone is actually a BooleanField and it exposes its data via the Value property.
Try doing a solution-wide search for : ContentField to see the classes for each of the fields you have available.
Hopefully this will have explained things clearly but I have actually written about using fields in a blog post and as part of my getting started with modules course over on the official docs (its way down in part 3 if you're curious).
Using built-in features instead of IsDone
This seems like a strange approach to do it this way. If you have a Content Item like a Page then you can just use the "Show on a menu" setting on the page.
Go to admin > content > open the page > down near the bottom you will find "Show on a menu":
This will automatically put it into your navigation and then you can move it around to where you want:
After it "IsDone" you can just go back and untick the "Show on a menu" option.
Setting up the alternative .cshtml
To clarify your comments about how to use the alternative, you need to
Copy the file you have at Orchard.Core/Shapes/Views/MenuItem.cshtml over to your theme's view folder so its /Views/MenuItem.cshtml
Rename the copy in your theme to MenuItem-ContentMenuItem.cshtml
Delete probably everything in it and paste in my sample at the start of this post. You don't want most of the original MenuItem.cshtml code in there as it is doing some special tricks to change itself into a different shape which isn't what you want.
Reset your original Orchard.Core/Shapes/Views/MenuItem.cshtml back to the factory default, grab it from the official Orchard repository
Understanding the view names
From your comments you asked about creating more specific views (known as alternates). You can use something call the Shape Tracer to view these. The name of them follows a certain pattern which makes them more and more specific.
You can learn about the alternates on the official docs site:
Accessing and Rendering Shapes
Alternates
To figure out what shape is being used and what alternates are available you can use the shape tracing module which is documented here:
Getting Started with Shape Tracing

Disable Lazy-Load Entity Framework Navigation Properties

Question
Is there any way to make it so that an entity returned from a DbContext query returns null (or some other specific value) when you try to access a navigation property that you did not specificly .Include()? For example:
var parents = dbContext.People.Where(p => p.Children.Any()).Include("Children").ToList();
//Assert all parents have children...
Assert.IsTrue(parents[0].Children.Any());
And...
var parents = dbContext.People.Where(p => p.Children.Any()).ToList();
//Assert all children collections are null... NOT LAZY LOADED
Assert.IsTrue(parents[0].Children == null);
To be clear, I do not want the property to be Eager-Loaded. I don't want it to be loaded at all. I've tried Detaching the entity from the context, but this doesn't help.
Background
The reason I am trying to do this is because i need to access the entity object on a diffrent thread than the one the DbContext was created on. Because of this, I do NOT want the navigation property to be set to some defered excution linq statement. The problem is that since I cannot access DbContext to check if a navigation property is loaded or not (due to it not being thread safe) I have no way of knowing if I need to create a new DbContext on the current thread to retrieve the missing data. This is the same problem that I was trying to solve with How to tell if a Navigation Property is loaded without DbContext
Update
Setting the DbContext's Configuration.LazyLoadingEnabled property to false prevents the linq from getting auto-wired up, but for navigation properties that are collections this results in a empty collection rather than null.
To solve the collection based problem, I Modified my T4 template to generate an empty default constructor rather than one that set each ICollection equal to an empty HashSet.
You can enable / disable lazy-loading by setting the Configuration.LazyLoadingEnabled property of your DbContext
context.Configuration.LazyLoadingEnabled = false;
var parents = dbContext.People.Where(p => p.Children.Any()).ToList();
context.Configuration.LazyLoadingEnabled = true;

Orchard CMS: Empty value in BodyPart

I'm developing custom News module for Orchard CMS. My NewsItem consists of few parts: TitlePart, CommonPart, BodyPart and my custom NewsPart.
When I try to create and save item, BodyPart becomes empty (other parts save correctly), and there is NULL value in DB. If I change Text column value in Common_BodyPartRecord table and then open item in editing mode, editor is empty, no value is loaded.
here is a definition of my NewsItem from migrations.cs:
ContentDefinitionManager.AlterTypeDefinition("NewsItem", t => t
.WithPart("NewsPart")
.WithPart("CommonPart", p => p.WithSetting("DateEditorSettings.ShowDateEditor", "true"))
.WithPart("TitlePart")
.WithPart("BodyPart")
.Draftable()
);
I've tried to debug BodyPartDriverclass. When I load news item editor in admin UI, Editor() method has input BodyPart part parameter. this is the instance of BodyPart attached to my NewsItem. And here part.Text is empty string. But part.base.Record.Text = "Test" as I set in DB table directly.
I can't understand what is wrong here. Other parts like TitlePart or my NewsPart are working fine. I do not have custom code with this BodyPart in my module. Seems I missed something, but what exactly?
In addition. There is an exception in Orchard Error log:
2014-10-22 17:25:15,307 [19] Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator -
Default - HttpRequestValidationException thrown from IContentPartDriver by
Orchard.Core.Common.Drivers.BodyPartDriver
http://localhost:30321/OrchardLocal/Admin/News/Edit/56
System.Web.HttpRequestValidationException (0x80004005): A potentially dangerous
Request.Form value was detected from the client (Body.Text="<p>123123123</p>").
PS: I've added [ValidateInput(false)] attribute to my NewsAdminController class as in default Orchard.Core.Contents.Controllers.AdminController. And now BodyPart saves correctly. But seems to me it is not the right way.
Never modify the database tables or the records directly. Go through the part's properties instead.
What's happening here is that this property is saved both in the record, and in the infoset. When you modify the record and not the infoset, next time you read the property, the infoset value wins and your record value gets overwritten. This is one of many reasons why you should never touch the records yourself.

MvvmCross indexed property bind not getting changes from view

I have a list on my view model that contains an object and property that I am trying to bind to from the main view.
var field = new UITextField();
Add(field);
this.AddBindings(
new Dictionary<object, string>()
{
{ field, "Text Names[0].Value" }
}
);
This will load fine and display the initial value from the view model but won't receive any values back when UITextField.Text is modified. It seems like it is not 2 way. Value is not a notification property but could be made into one if that is what this setup requires.
I also tried the following but that does work at all:
set.Bind(field).To(vm => vm.Names[0].Value);
Using MvvmCross 3.0.9. Xamarin.iOS 6.3.7
I've just tested this using 3.0.9 with a ViewModel which has an ObservableCollection of non-INPC Thing objects and this two way binding seems to work OK for me - both when the ObservableCollection changes and when the UITextField value changes because of user action.
Can you provide any more of your ViewModel nad list object code? (If not, can you reproduce the error in a simple sample?)
Can you provide any error trace you are seeing (in debug|output)? (This may need enabling - see MvvmCross Mvx.Trace usage)
Can you provide any more information on the form that "does work at all" - this might be a clue about what is going wrong.

Resources