Using below code, I have two shape results:
public ActionResult CompareRevisions(List<String> Ids)
{
contentItemLeft = // code to get a ContentItem
contentItemRight = // code to get a ContentItem
dynamic modelLeft = Services.ContentManager.BuildDisplay(contentItemLeft);
dynamic modelRight = Services.ContentManager.BuildDisplay(contentItemRight);
var ctx = Services.WorkContext;
ctx.Layout.Metadata.Alternates.Add("Layout_Null");
var shapeResultLeft = new ShapeResult(this, modelLeft);
var shapeResultRight = new ShapeResult(this, modelRight);
return shapeResultLeft;
}
When I return any of one shape result such as return shapeResultLeft at the last line of Controller, the browser displays perfectly the Content. However How can I display both of my ShapeResults: shapeResultLeft , shapeResultRight on the Page same time ?
How do I return a list of ShapeResults and display it using the View/Layout file ?
You have multiple options for this:
Method 1
one most used in MVC (not Orchard specific) is a viewmodel:
public class MyViewModel {
public dynamic Shape1 { get; set; }
public dynamic Shape2 { get; set; }
}
public ActionResult CompareRevisions(List<String> Ids) {
// ..
var viewModel = new MyViewModel {
Shape1 = modelLeft,
Shape2 = modelRight
}
return View(viewModel)
}
view:
#model My.NameSpace.ViewModels.MyViewModel
#Display(Model.Shape1)
#Display(Model.Shape2)
Method 2
Without using strongly typed viewmodels, you can use orchard's dynamic viewmodel:
// inject IShapeFactory through Dependency Injection
public MyController(IShapeFactory shapeFactory) {
Shape = shapeFactory;
}
public dynamic Shape { get; set; } // inject with DI through IShapeFactory
public ActionResult CompareRevisions(List<String> Ids) {
// ..
var viewModel = Shape
.ViewModel() // dynamic
.Shape1(modelLeft)
.Shape2(modelRight);
return View(viewModel);
}
Method 3
Or with Orchard's list, when the number of shapes could vary:
public dynamic Shape { get; set; } // inject with DI through IShapeFactory
public ActionResult CompareRevisions(List<String> Ids) {
// ..
var list = Shape.List();
list.AddRange(myShapes); // myShapes is a collection of build shapes (modelLeft, modelRight)
var viewModel = Shape
.ViewModel()
.List(list);
return View(viewModel);
}
view:
#Display(Model.List);
Related
What I'm trying to do is create a site in Orchard that doesn't have a way for a user to register. An administrator will create the users.
What I have is module that defines the parts, records, views, etc. That is basically working.
Now what I'm trying to do is add a UserPart (from Orchard.Users) to one of the parts in my module.
I'm not sure how to do that. I need the fields displayed for the UserPart with the fields for the parent part in the same view. This also needs to be done in a way that when a save happens, all of the UserPart fields get sent to the Orchard.Users module.
Any suggestions, pointers or links on how to do that?
Thanks!
UPDATE...
The Activating Filter is an interesting idea. I initially chose the migration route. For now, I'll try and get that method working.
For simplicity, let's say I have a "Company" type (there's more to the actual type) that has a "CompanyName" and a UserPart.
Here's what the different pieces look like...
Migrations.cs (simplified)
public int Create()
{
SchemaBuilder.CreateTable("CompanyPartRecord", table => table.ContentPartRecord()
.Column("CompanyName", DbType.AnsiString, c => c.WithLength(50))
.Column("UserId", DbType.Int32));
SchemaBuilder.CreateForeignKey("FK_CompanyPartRecord_UserPartRecord", "CompanyPartRecord", new[] {"UserId" }, "Orchard.Users", "UserPartRecord", new[] { "Id" })
ContentDefinitionManager.AlterTypeDefinition("Company", type => type.WithPart("CommonPart").WithPart("UserPart"));
}
CompanyPartRecord
public class CompanyPartRecord : ContentPartRecord
{
public virtual string CompanyName { get; set; }
public virtual int? UserId { get; set; }
}
CompanyPart
public class CompanyPart : ContentPart<CompanyPartRecord>
{
internal LazyField<UserPart> UserPartField = new LazyField<UserPart>();
public string CompanyName
{
get { return Record.CompanyName; }
set { Record.CompanyName = value; }
}
public UserPart User
{
get { return UserPartField.Value;}
set { UserPartField.Value = value; }
}
}
Handler
public class CompanyPartHandler : ContentHandler
{
private readonly IContentManager _manager;
public CompanyPartHandler(IRepository<CompanyPartRecord> repository, IContentManager manager)
{
_manager = manager;
Filters.Add(StorageFilter.For(repository));
OnActivated<CompanyPart>(OnActivatedHandler);
}
private void OnActivatedHandler(ActivatedContentContext context, CompanyPart part)
{
if(part.User == null)
{
part.User = _manager.Create<UserPart>("User");
}
else
{
part.User = _manager.Get<UserPart>(part.User.Id);
}
}
}
Driver
public class CompanyPartDriver : ContentPartDriver<CompanyPart>
{
protected override DriverResult Editor(CompanyPart part, dynamic shapeHelper)
{
return ContentShape("Parts_Company_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/Company",
Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(CompanyPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
Controller
public class AdminCompanyController : Controller, IUpdateModel
{
private readonly IOrchardServices _services;
private readonly INotifier _notifier;
private readonly IContentManager _contentManager;
private readonly ITransactionManager _transactionManager;
private readonly Localizer T = NullLocalizer.Instance;
public AdminCompanyController(IOrchardServices services)
{
_services = services;
_notifier = services.Notifier;
_contentManager = services.ContentManager;
_transactionManager = services.TransactionManager;
}
public ActionResult Create()
{
var company = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.BuildEditor(company);
return View(model);
}
[HttpPost, ActionName("Create")]
public ActionResult CreatePOST()
{
var contentItem = _contentManager.New<CompanyPart>("Company");
var model = _contentManager.UpdateEditor(contentItem, this);
if (!ModelState.IsValid)
{
_transactionManager.Cancel();
return View(model);
}
_contentManager.Create(contentItem.ContentItem);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Edit(int Id)
{
var contentItem = _services.ContentManager.Get(Id);
dynamic model = _services.ContentManager.BuildEditor(contentItem);
return View(model);
}
[HttpPost, ActionName("Edit")]
public ActionResult EditPOST(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
var model = _contentManager.UpdateEditor(contentItem, this);
_notifier.Information(T("Company has been saved"));
return RedirectToAction("Index");
}
public ActionResult Delete(int Id)
{
var contentItem = _contentManager.Get<CompanyPart>(Id);
_contentManager.Destroy(contentItem.ContentItem);
return RedirectToAction("Index");
}
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties)
{
return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
public void AddModelError(string key, LocalizedString errorMessage)
{
ModelState.AddModelError(key, errorMessage.ToString());
}
}
View (create)
#{ Layout.Title = T("Add Company").ToString(); }
#using (Html.BeginFormAntiForgeryPost())
{
#Display(Model)
}
Editor Template
#model SDS.Models.CompanyPart
<fieldset>
#Html.LabelFor(m => m.CompanyName)
#Html.TextBoxFor(m => m.CompanyName)
</fieldset>
#*
What goes here to display UserPart?
*#
So here's where I'm at. I can see the ContentItem (CompanyType). I can put in the name and save it. The name is getting saved to the db. Right now the UserPart is getting saved to the db, but all of the fields are blank.
The part I'm stuck on is what to put in the editor template to display the UserPart fields so that the values get to the UserPart driver and ultimately the db.
Any ideas on how to do that?
Thanks!
So you don't attach parts to parts, you attach parts to content items, and you can do that in multiple ways.
You can do it through the admin screen, but that isn't a code driven solution and would have problems if you have multiple environments or need to redeploy a fresh version of code.
You can attach the part when you create a new content item in the migration. This might be a good solution, if you already ran your migration you could possibly do it with an update migration. This allows the part to be managed through the admin screen, but has downsides because it can be removed and if you have code that relies on the part then you will start having errors.
The last way and best way is to attach the part dynamically using an Activating Filter.
ActivatingFilter class - Attaches a part to a content type from code. As opposed to attaching parts via migrations, parts attached using this filter will neither be displayed in the Dashboard, nor users will be able to remove them from types. It's a legitimate way of attaching parts that should always exist on a given content type.
So to do this:
1. Add a reference to Orchard.Users to your custom project.
2. Create a handler for you part. Such as MyPartHandler
3. Then add the activating handler like so
Filters.Add(ActivatingFilter.For<UserPart>("MyContentType"));
So now anywhere in your code you can access the UserPart if you already have your part, or the content item using
var userPart = myPart.As<UserPart>();
I have an autocompleter as a partial view which I would like to use twice in the same page. There are three models involved
public partial class CustomParent
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 CustomParentId { get; set; }
public Geoname ParentGeoname { get; set; }
public Geoname ChildGeoname { get; set; }
public CustomParent()
{
ParentGeoname = new Geoname();
ChildGeoname = new Geoname();
}
}
public partial class Geoname
{
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Int64 GeonameId { get; set; }
public string Name { get; set; }
}
public partial class GeonameWithFilter
{
public GeonameWithFilter()
{
FilterString = "";
}
public Geoname Geoname { get; set; }
public String FilterString { get; set; }
}
I have a controller set up
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "CustomParentId,ParentGeoname,ChildGeoname")] CustomParent customParent)
{...}
I set up the two Html partials on my create view initially only using the customparent and geoname models and it worked fine setting the values in of the parent and child geonames as expected. I now require additional parameters to be passed to the partial view so I created an encapsulating class (GeonameWithFilter). I made changes to my partial view and to my two html.partials on the view page which now look like this :
#Html.Partial("_GeonameAutocomplete",new GeonameWithFilter(){Geoname = Model.ParentGeoname,FilterString="'featurecodes':'CUPA'"},
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", new GeonameWithFilter() { Geoname = Model.ChildGeoname, FilterString = "'featurecodes':'ADM1,ADM2,ADM3,ADM4'" },
new ViewDataDictionary()
{
TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "ChildGeoname" }
})
The problem is that the customparent.parentGeoname and customparent.childGeoname are not now getting returned to my controller. I'm guessing this is because the partial view's model is not the same class as my page models parentGeoname and childGeoname but cannot work out or find any examples of how to handle such a circumstance if indeed it is possible.
Well - it took me most of a day, but I now have what I required.
I gave up on the encapsulation and instead added my basic geoname model to the new ViewDataDictionary and nullified the default model for the Html.Partial.
I then added the filterstring parameter as a key in a new ViewDataDictionary which I used as the argument for the one with the model and TemplateInfo.
Many thanks to https://stackoverflow.com/users/7714/craig-stuntz for his answer to a different question Shorthand for creating a ViewDataDictionary with both a model and ViewData items? that pointed me in the direction I have gone.
My autocompleter now just use #ViewData["FilterString"] to access the filter parameters. The GeonameWithFilter encapsulation is no longer needed. My two Html.Partials on my view page now look like this:
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { {"FilterString", "featurecodes:'CUPA'" }})
{
Model = Model.ParentGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ParentGeoname" }
})
#Html.Partial("_GeonameAutocomplete", null,
new ViewDataDictionary(new ViewDataDictionary() { { "FilterString", "featurecodes:'ADM1,ADM2,ADM3,ADM4'" } })
{
Model = Model.ChildGeoname,
TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "ChildGeoname" }
})
If anyone knows a better way to achieve the end result I would still like to hear it.
This is an editor for WebShop Global Settings. I needed to extend the editor with a ViewModel. It worked fine before I started but now crashes with the above error when it's invoked. What am I doing wrong?
Here's the driver:
public class WebShopSettingsPartDriver : ContentPartDriver<WebShopSettingsPart>
{
private readonly ISiteService _siteService;
private readonly IWebshopSettingsService _webshopSettings;
protected override string Prefix { get { return "WebShopSettings"; } }
private const string shapeName = "Parts_WebShopSettings_Edit";
private const string templateName = "Parts/WebShopSettings";
public WebShopSettingsPartDriver(IWebshopSettingsService webshopSettings, ISiteService siteService)
{
_webshopSettings = webshopSettings;
_siteService = siteService;
}
protected override DriverResult Editor(WebShopSettingsPart part, dynamic shapeHelper)
{
var settings = _siteService.GetSiteSettings().As<WebShopSettingsPart>();
var model = new WebShopSettingsVM
{
WebShopSettings = settings,
ShippingProducts = _webshopSettings.ShippingProductRecords()
};
return ContentShape(shapeName,
() => shapeHelper.EditorTemplate(TemplateName: templateName, Model: model, Prefix: Prefix)).OnGroup("WebShop");
}
}
}
Here is the Handler:
public class WebShopSettingsPartHandler : ContentHandler {
public WebShopSettingsPartHandler(IRepository<WebShopSettingsRecord> repository) {
T = NullLocalizer.Instance;
Filters.Add(new ActivatingFilter<WebShopSettingsPart>("Site"));
Filters.Add(StorageFilter.For(repository));
OnGetContentItemMetadata<WebShopSettingsPart>((context, part) => context.Metadata.EditorGroupInfo.Add(new GroupInfo("WebShop")));
}
}
And here is the first line of the View (which is in Views\EditorTemplates\Parts\WebShopSettings.cshtml):
#model Cascade.WebShop.ViewModels.WebShopSettingsVM
The Placement.ini file has the following entry:
<Place Parts_WebShopSettings_Edit="Content:0" />
Here is the ViewModel:
public class WebShopSettingsVM
{
public IEnumerable<ShippingProductRecord> ShippingProducts{ get; set; }
[Required]
public int? ShippingProductRecordId { get; set; }
public WebShopSettingsPart WebShopSettings { get; set; }
// Expose all the properties of the Part directly on the VM
[Required]
public string AdministratorEmailAddress
{
get { return WebShopSettings.AdministratorEmailAddress; }
set { WebShopSettings.AdministratorEmailAddress = value; }
}
[Required]
public string ContinueShoppingUrl
{
get { return WebShopSettings.ContinueShoppingUrl; }
set { WebShopSettings.ContinueShoppingUrl = value; }
}
// and so on...
}
After Bertrand's suggestion below I updated the View to:
#using Cascade.WebShop.ViewModels
#using Cascade.WebShop.Models
#{
var vm = Model.Model as WebShopSettingsVM;
}
<fieldset>
<legend>#T("Webshop")</legend>
<div>
<label for="#Html.FieldIdFor(x=>vm.AdministratorEmailAddress)">#T("Administrator email address")</label>
#Html.TextBoxFor(x=>vm.AdministratorEmailAddress, new { #class = "textMedium" })
#Html.ValidationMessage("AdministratorEmailAddress", "*")
...
Insights and suggestions greatly appreciated -- I simply can't see what's wrong.
A second copy of the driver, under a slightly different name, was present in the 'Helpers' directory. Not surprisingly, I didn't notice this. The 'Helper' driver was supplying a Part and the 'proper' driver a VM. Both were being fired and thus whether I used a VM or a Part one or other of the two drivers would throw an exception.
Deleting the spurious driver fixed the problem. Sorry Bertrand.
The model is still the shape. Remove the directive, and access Model.Model to access your view model: Model is the shape, and Model.Model is the property named Model that is on the shape.
Just cast Model.Model to the view model's type.
I have one question about Windows Workflow Foundation 4. I have an activity named PositionArrayActivity. This activity has a Sequence activity inside it. I need that in Execute method (during the workflow execution) oneFund variable mapping his value to PORTFOLIO_NAME that is created in Create method.... What have I to do to mapping oneFund value to PORTFOLIO_NAME at runtime?
Thanks
public sealed class PositionArrayActivity : NativeActivity, IActivityTemplateFactory
{
[Browsable(false)]
public Dictionary<string, List<Entity>> dictionary = new Dictionary<string, List<Entity>>();
public ActivityAction<Entity[]> Body { get; set; }
public Entity[] PositionList { get; set; }
public SqlDataReader rdr;
public SqlDataReader sdr;
public Entity[] positionArray;
public List<String> fundList;
public String oneFund { get; set; }
public String date { get; set; }
public List<Entity> listToArrayPositions;
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.AddDelegate(Body);
}
protected override void Execute(NativeActivityContext context)
{
// A lot of code....
}
public Activity Create(DependencyObject target)
{
Variable<string> var = new Variable<string>
{
Name = "PORTFOLIO_NAME"
};
var fef = new PositionArrayActivity();
var aa = new ActivityAction<Entity[]>();
var da = new DelegateInArgument<Entity[]>();
da.Name = "positions";
fef.Body = aa;
aa.Argument = da;
aa.Handler = new Sequence
{
Variables = { var }
};
return fef;
}
}
You need to have an ActivityContext to set a variable value so first move the declaration of the var (did that name actually compile?) to a higher scope.
Then in Execute
var.Set(activityContext, oneFund);
One thing though, the oneFund property will only be set once at application startup so you may have some surprising results. If you wanted that to be for each instance, you need an inargument.
I am trying to implement a Settings dialog for iOS using MonoTouch and the DialogViewController.
The class below contains some public properties, and a method to get a DialogViewController for it.
The problem is that when the view disappears, the string value in thisName.Value is null (I have of course filled in something in the text field).
Why?
public class Settings
{
public string Name { get; set; }
public int MagicNumber { get; set; }
public bool ThisIsEnabled{ get; set; }
public Settings ()
{
var defaults = NSUserDefaults.StandardUserDefaults;
Name = defaults.StringForKey ("name");
ThisIsEnabled = defaults.BoolForKey("thisisenabled");
MagicNumber = defaults.IntForKey ("123");
}
public UIViewController GetViewController ()
{
var thisBoolean = new BooleanElement ("This boolean", ThisIsEnabled);
var thisName = new EntryElement ("Name", "name", Name);
thisName.KeyboardType = UIKeyboardType.ASCIICapable;
var root = new RootElement ("Options"){
new Section (){thisBoolean,thisName}
};
var dv = new DialogViewController (root, true){Autorotate= true};
dv.ViewDissapearing += delegate {
ThisIsEnabled = thisBoolean.Value; // <== This works
Name = thisName.Value; // <== This is NULL
var defaults = NSUserDefaults.StandardUserDefaults;
defaults.SetBool (ThisIsEnabled, "thisisenabled");
defaults.SetString (Name, "name");
};
return dv;
}
}
}
the current release of MT.D will not "save" a field's value until the user navigates away from it. This may be what you are seeing.
This behavior has been fixed, but has not been released yet.