Orchard CMS 1.7 - Extend Media Content Type to get Tag type functionality - orchardcms-1.7

Problem:
I have situation where I have to tag the media items with geoIDs. I have been trying to replicate the functionalists of Tags Module and have created the models, Views, Drives and Handler for GeoObject. My problem is that when I load the edit view of an Image, I don't get my GeoObject edit view.
Here's my Handler:
class GeoObjectsPartHandler:ContentHandler {
public GeoObjectsPartHandler(IRepository<GeoObjectsPartRecord> repository, IGeoObjectService geoObjectService)
{
Filters.Add(StorageFilter.For(repository));
OnIndexing<GeoObjectsPart>(
(context, geoObjectsPart) =>
{
foreach (var geoObject in geoObjectsPart.CurrentGeoObjects)
{
context.DocumentIndex.Add("geoObjects", geoObject.GeoObjectName).Analyze();
}
});
}
}
Driver:
[UsedImplicitly]
class GeoObjectsPartDriver: ContentPartDriver<GeoObjectsPart>
{
private static readonly char[] _disalowedChars = new[] { '<', '>', '*', '%', ':', '&', '\\', '"', '|' };
private const string TemplateName = "Parts/GeoObjects";
private readonly INotifier _notifier;
private readonly IGeoObjectService _geoObjectService;
public GeoObjectsPartDriver(IGeoObjectService geoObjectService, INotifier notifier)
{
_geoObjectService = geoObjectService;
_notifier = notifier;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
protected override string Prefix
{
get { return "GeoObjects"; }
}
protected override DriverResult Editor(GeoObjectsPart part, dynamic shapeHelper)
{
return ContentShape("Parts_GeoObjects_Edit",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: BuildEditorViewModel(part), Prefix: Prefix));
}
protected override DriverResult Editor(GeoObjectsPart part, IUpdateModel updater, dynamic shapeHelper)
{
var model = new EditGeoObjectsViewModel();
return ContentShape("Parts_GeoObjects_Edit",
() => shapeHelper.EditorTemplate(TemplateName: TemplateName, Model: model, Prefix: Prefix));
}
private static EditGeoObjectsViewModel BuildEditorViewModel(GeoObjectsPart part)
{
return new EditGeoObjectsViewModel
{
GeoObjects = string.Join(", ", part.CurrentGeoObjects.Select((t, i) => t.GeoObjectName).ToArray())
};
}
protected override void Importing(GeoObjectsPart part, ImportContentContext context)
{
var geoObjectString = context.Attribute(part.PartDefinition.Name, "GeoObjects");
}
protected override void Exporting(GeoObjectsPart part, ExportContentContext context)
{
context.Element(part.PartDefinition.Name).SetAttributeValue("GeoObjects", String.Join(",", part.CurrentGeoObjects.Select(t => t.GeoObjectName)));
}
}
Migration:
using Orchard.Data.Migration;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;
namespace ePageo.TUI.MediaManager
{
public class MediaManagerDataMigration : DataMigrationImpl
{
public int Create()
{
SchemaBuilder.CreateTable("GeoObjectsPartRecord",
table => table
.ContentPartRecord()
);
SchemaBuilder.CreateTable("GeoObjectRecord",
table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<string>("GeoObjectName")
);
SchemaBuilder.CreateTable("ContentGeoObjectRecord",
table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<int>("GeoObjectRecord_Id")
.Column<int>("GeoObjectsPartRecord_Id")
);
ContentDefinitionManager.AlterPartDefinition("GeoObjectsPart", builder => builder.Attachable());
return 1;
}
public int UpdateFrom1()
{
ContentDefinitionManager.AlterPartDefinition("GeoObjectsPart", builder => builder
.WithDescription("Allows to add Geo-object ids to the particular media Item."));
return 2;
}
public int UpdateFrom2()
{
ContentDefinitionManager.AlterTypeDefinition("Image", td => td
.WithPart("GeoObjectsPart")
);
ContentDefinitionManager.AlterTypeDefinition("Video", td => td
.WithPart("GeoObjectsPart")
);
ContentDefinitionManager.AlterTypeDefinition("Audio", td => td
.WithPart("GeoObjectsPart")
);
ContentDefinitionManager.AlterTypeDefinition("Document", td => td
.WithPart("GeoObjectsPart")
);
ContentDefinitionManager.AlterTypeDefinition("OEmbed", td => td
.WithPart("GeoObjectsPart")
);
return 3;
}
}
}
Placement.info:
<Placement>
<Place Parts_GeoObjects_Edit="Content:12"/>
</Placement>
I don't think I have problem in my model since its the exact replication of Orchard Tags Models. In fact all of the above files are just that.
I just cannot get the Geo Object edit view to show up in Image (Media) edit view.
I need help!

I got the code to work.
Turns out I had to declare the Driver and Handler classes to public.
I just switched from PHP to ASP.NET few months ago so since if we did not declare any scope PHP would take it as public, I thought it'd be the same with ASP.NET.
The code itself had no other problem except for that.

Related

.NET efcore 7 JSON COLUMN PROBLEMS

as you know, jsoncolumn support has arrived for efcore7.
I quickly used
yes, I had no problems with creating columns with migration. I added new data
but i have the following problem in query operation
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<IdentitySchema>().OwnsMany(
identitySchema => identitySchema.AuthorityCodes, ownedNavigationBuilder =>
{
var q=ownedNavigationBuilder.ToJson();
})
.OwnsMany(
identitySchema => identitySchema.UserIds, ownedNavigationBuilder =>
{
var x= ownedNavigationBuilder.ToJson();
}); ;
base.OnModelCreating(builder);
}
public class UserAg
{
public string UserId { get; set; }
}
_context.IdentitySchema.Select(f => new
{
f.UserIds,
f.AuthorityCodes
}).Where(f => f.UserIds.Any(f => f.UserId == "1")).ToList();

Symfony Zenstruck Foundry embedded entity without duplicated property

I have an entity MultiChannel that has a OneToMany relation with SalesChannel.
The restriction is that MultiChannel cannot have 2 SalesChannels that have the same name property.
Initially I had created the Story below, but that will endup with SalesChannels that have the same name.
App/Tests/Story/MultiChannelStory
final class MultiChannelStory extends Story
{
public function build(): void
{
SalesChannelFactory::createMany(100);
MultiChannelFactory::createMany(50, function() {
return [
'salesChannel' => SalesChannelFactory::randomRange(1,3),
];
});
}
}
Then I created a SalesChannelStory as below:
App/Tests/Story/SalesChannelStory
final class SalesChannelStory extends Story
{
public function build(): void
{
$this->addToPool('name1', SalesChannelFactory::new(['name' => SalesChannelFactory::SALES_CHANNELS[0]])->many(50));
$this->addToPool('name2', SalesChannelFactory::new(['name' => SalesChannelFactory::SALES_CHANNELS[1]])->many(50));
$this->addToPool('name3', SalesChannelFactory::new(['name' => SalesChannelFactory::SALES_CHANNELS[2]])->many(50));
$this->addToPool('name4', SalesChannelFactory::new(['name' => SalesChannelFactory::SALES_CHANNELS[3]])->many(50));
}
}
The intention was to do something as below on MultiChannelStory, in somewhat pseudo code, so that
I could insert only uniquely named SalesChannel into MultiChannel:
App/Tests/Story/MultiChannelStory
final class MultiChannelStory extends Story
{
public function build(): void
{
SalesChannelFactory::createMany(100);
MultiChannelFactory::createMany(50, function() {
return [
'salesChannel' => $this->getUniqueNamed(),,
];
});
}
}
private function getUniqueNamed(): \App\Entity\Onetomanybi|\Zenstruck\Foundry\Proxy
{
// get up to 4 SalesChannel without the property `name` being repeated.
$items = [SalesChannelStory::getRandom('name1'), SalesChannelStory::getRandom('name2')];
return $items;
//return SalesChannelStory::getRandom('name1');
}
But that does not work.
Note that MultiChannel has to have at least one SalesChannel, up to as many SalesChannel exists, or 4 currently.

FluentValidation set validator on collection with Ruleset

I'm creating a POST Rest API(ASP.NET Web API) to perform write operations,so have to validate data before inserting them into Database.I'm pretty new to using FluentValidation to validate the data.
Suppose below are classes that I have and need to validate.
public class Listing
{
public Type Type { get; set; }
public Source Source { get; set; }
public List<ListingDetails> ListingDetails { get;set; }
}
public class ListingDetails
{
public int Id{ get; set; }
public ListingStatus Status { get; set; }
}
public enum ListingStatus
{
Active = 1,
Converted = 2,
LostToCompetitor = 3
}
Below code is responsible for validating the status based on the provided ruleset.
public class ListingStatusValidator : AbstractValidator<ListingDetails>
{
public ListingStatusValidator()
{
RuleSet("A", () =>
{
RuleFor(x=>x.InquiryId).GreaterThan(0);
});
RuleSet("B", () =>
{
RuleFor(x => x.Status).IsInEnum().NotEqual(ListingStatus.Active);
});
RuleSet("C", () =>
{
RuleFor(x => x.Status).IsInEnum();
});
}
}
Below is the piece of code used to validatelisting.
public class ListingValidator : AbstractValidator<Listing>
{
public ListingValidator()
{
RuleSet("common", () =>
{
When(x => x.ListingDetails != null && x.ListingDetails.Count <= 1000, () =>
RuleForEach(x => x.ListingDetails).SetValidator(new ListingStatusValidator()));
});
}
}
Now to validate we will call validate method of the validator like below.
var validation = new ListingValidator().Validate(listing,ruleSet:"common");
Is it possible to pass ruleset when validating using setvalidator on collection of objects.Please see below snippet to understand what I'm trying to achieve.
public class ListingValidator : AbstractValidator<Listing>
{
public ListingValidator()
{
When(x => x.ListingDetails != null && x.ListingDetails.Count <= 1000, () =>
RuleForEach(x => x.ListingDetails).SetValidator(new ListingStatusValidator(),ruleset:"A,B,C"));
}
}
You can execute more then one RuleSet using RulesetValidatorSelector
var validation = new ListingValidator()
.Validate(listing, new RulesetValidatorSelector("common", "A", "B", "C"));
In this case you don't need to specify RuleSets for ListingStatusValidator, RuleSets from ListingValidator will be passed to nested validator.

Empty content item after create/edit in Orchard

I'm using the guide to creating n-to-n relations in Orchard (ocs.orchardproject.net/Documentation/Creating-1-n-and-n-n-relations) with some slight modifications. While the sample code works well my own content part is always blank after I create or edit the item. I can't figure it out, because I swear my code is almost identical to theirs (with the exception of content parts having more/less unrelated fields).
I suspect it might have to do with the Prefix in the Driver. I don't really know what the prefix is supposed to do, but setting it to one value produces a runtime error on create/edit, other values just produce result with all fields blank.
The original sample works fine, so it has to be something I did or didn't do, but I just can't figure out what it is.
Some relevant classes:
using System.Linq;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using ArealAds.Models;
using ArealAds.Services;
using ArealAds.ViewModels;
namespace ArealAds.Drivers {
[UsedImplicitly]
public class StreetPartDriver : ContentPartDriver<StreetPart> {
private readonly IStreetService _streetService;
private const string TemplateName = "Parts/Street";
public StreetPartDriver(IStreetService streetService) {
_streetService = streetService;
}
// this one gives a runtime error with blank description,
// other values produce result with all fields blank
protected override string Prefix {
get { return "Area"; }
}
protected override DriverResult Display(StreetPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Street",
() => shapeHelper.Parts_Street(
ContentPart: part,
Name: part.Name,
Areas: part.Areas,
Districts: part.Districts));
}
protected override DriverResult Editor(StreetPart part, dynamic shapeHelper) {
return ContentShape("Parts_Street_Edit",
() => shapeHelper.EditorTemplate(
TemplateName: TemplateName,
Model: BuildEditorViewModel(part),
Prefix: Prefix));
}
protected override DriverResult Editor(StreetPart part, IUpdateModel updater, dynamic shapeHelper) {
var model = new EditStreetViewModel();
updater.TryUpdateModel(model, Prefix, null, null);
if (part.ContentItem.Id != 0) {
_streetService.UpdateAreasForContentItem(part.ContentItem, model.Areas);
}
return Editor(part, shapeHelper);
}
private EditStreetViewModel BuildEditorViewModel(StreetPart part) {
var itemAreas = part.Areas.ToLookup(r => r.Id);
return new EditStreetViewModel {
Areas = _streetService.GetAreas().Select(r => new AreaEntry {
Area = r,
IsChecked = itemAreas.Contains(r.Id)
}).ToList()
};
}
}
}
using System.Collections.Generic;
using System.Linq;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Data;
using ArealAds.Models;
using ArealAds.ViewModels;
namespace ArealAds.Services {
public interface IStreetService : IDependency {
void UpdateAreasForContentItem(ContentItem item, IEnumerable<AreaEntry> areas);
IEnumerable<AreaRecord> GetAreas();
}
public class StreetService : IStreetService {
private readonly IRepository<AreaRecord> _areaRepository;
private readonly IRepository<StreetAreaRecord> _streetAreaRepository;
public StreetService(
IRepository<AreaRecord> areaRepository,
IRepository<StreetAreaRecord> streetAreaRepository) {
_areaRepository = areaRepository;
_streetAreaRepository = streetAreaRepository;
}
public void UpdateAreasForContentItem(ContentItem item, IEnumerable<AreaEntry> areas) {
var record = item.As<StreetPart>().Record;
var oldAreas = _streetAreaRepository.Fetch(
r => r.StreetRecord == record);
var lookupNew = areas
.Where(e => e.IsChecked)
.Select(e => e.Area)
.ToDictionary(r => r, r => false);
// Delete the areas that are no longer there and mark the ones that should stay
foreach(var streetAreaRecord in oldAreas) {
if (lookupNew.ContainsKey(streetAreaRecord.AreaRecord)) {
lookupNew[streetAreaRecord.AreaRecord] = true;
}
else {
_streetAreaRepository.Delete(streetAreaRecord);
}
}
// Add the new areas
foreach(var area in lookupNew.Where(kvp => !kvp.Value).Select(kvp => kvp.Key)) {
_streetAreaRepository.Create(new StreetAreaRecord {
StreetRecord = record,
AreaRecord = area
});
}
}
public IEnumerable<AreaRecord> GetAreas() {
return _areaRepository.Table.ToList();
}
}
}
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class StreetAreaRecord : ContentPartRecord {
public virtual StreetRecord StreetRecord { get; set; }
public virtual AreaRecord AreaRecord { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Data;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.Core.Contents.Extensions;
using Orchard.Data.Migration;
using ArealAds.Models;
namespace ArealAds {
public class Migrations : DataMigrationImpl {
public int Create() {
//
// Street-Area-District
//
SchemaBuilder.CreateTable("DistrictRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(DistrictPart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition(
"District", cfg => cfg
.WithPart("CommonPart")
.WithPart("DistrictPart")
.Creatable()
);
SchemaBuilder.CreateTable("AreaRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
.Column<int>("DistrictRecord_Id")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(AreaPart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition(
"Area", cfg => cfg
.WithPart("CommonPart")
.WithPart("AreaPart")
.Creatable()
);
SchemaBuilder.CreateTable("StreetRecord", table => table
.ContentPartRecord()
.Column<string>("Name")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(StreetPart).Name, cfg => cfg.Attachable());
ContentDefinitionManager.AlterTypeDefinition(
"Street", cfg => cfg
.WithPart("CommonPart")
.WithPart("StreetPart")
.Creatable()
);
SchemaBuilder.CreateTable("StreetAreaRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column<int>("StreetRecord_Id")
.Column<int>("AreaRecord_Id")
);
//
// Address-Ad
//
SchemaBuilder.CreateTable("AddressRecord", table => table
.ContentPartRecord()
.Column<int>("StreetRecord_Id")
.Column<int>("Building")
.Column<int>("Kor")
.Column<int>("Str")
.Column<int>("Vl")
.Column<string>("Note")
.Column<int>("AreaRecord_Id")
.Column<int>("DistrictRecord_Id")
.Column<string>("Phone1")
.Column<string>("Phone2")
.Column<string>("Phone3")
);
ContentDefinitionManager.AlterPartDefinition(
typeof(AddressPart).Name, cfg => cfg.Attachable());
return 1;
}
}
}
#model ArealAds.ViewModels.EditStreetViewModel
<fieldset>
<legend>Улица</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Street.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.Street.Name)
#Html.ValidationMessageFor(model => model.Street.Name)
</div>
<ul>
#for (int i = 0; i < Model.Areas.Count; i++) {
<li>
<input type="hidden" value="#Model.Areas[i].Area.Id"
name="#Html.FieldNameFor(m => m.Areas[i].Area.Id)"/>
<label for="#Html.FieldNameFor(m => m.Areas[i].IsChecked)">
<input type="checkbox" value="true"
name="#Html.FieldNameFor(m => m.Areas[i].IsChecked)"
id="#Html.FieldNameFor(m => m.Areas[i].IsChecked)"
#if (Model.Areas[i].IsChecked) {<text>checked="checked"</text>}/>
#Model.Areas[i].Area.Name
</label>
</li>
}
</ul>
</fieldset>
I had been beating my head against the wall on this for days, please make any suggestions you feel might theoretically help 'cos I'm desperate :(
UPD: The StreetHandler class:
using ArealAds.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
namespace ArealAds.Handlers {
public class StreetHandler : ContentHandler {
public StreetHandler(IRepository<StreetRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}
There is an exception on the log:
2012-04-10 00:07:58,515 [7] Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator - IdentifierGenerationException thrown from IContentPartDriver by ArealAds.Drivers.StreetPartDriver
NHibernate.Id.IdentifierGenerationException: attempted to assign id from null one-to-one property: ContentItemRecord
â NHibernate.Id.ForeignGenerator.Generate(ISessionImplementor sessionImplementor, Object obj)
â NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
â NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
â NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
â NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
â NHibernate.Impl.SessionImpl.Save(Object obj)
â Orchard.Data.Repository`1.Create(T entity) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\Data\Repository.cs:ñòðîêà 96
â Orchard.Data.Repository`1.Orchard.Data.IRepository<T>.Create(T entity) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\Data\Repository.cs:ñòðîêà 36
â ArealAds.Services.StreetService.UpdateAreasForContentItem(ContentItem item, IEnumerable`1 areas) â c:\Users\Mom\Teritoriya\Modules\ArealAds\Services\Street.cs:ñòðîêà 46
â ArealAds.Drivers.StreetPartDriver.Editor(StreetPart part, IUpdateModel updater, Object shapeHelper) â c:\Users\Mom\Teritoriya\Modules\ArealAds\Controllers\Street.cs:ñòðîêà 47
â System.Dynamic.UpdateDelegates.UpdateAndExecute4[T0,T1,T2,T3,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3)
â Orchard.ContentManagement.Drivers.ContentPartDriver`1.Orchard.ContentManagement.Drivers.IContentPartDriver.UpdateEditor(UpdateEditorContext context) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\ContentManagement\Drivers\ContentPartDriver.cs:ñòðîêà 30
â Orchard.ContentManagement.Drivers.Coordinators.ContentPartDriverCoordinator.<>c__DisplayClass10.<UpdateEditor>b__f(IContentPartDriver driver) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\ContentManagement\Drivers\Coordinators\ContentPartDriverCoordinator.cs:ñòðîêà 61
â Orchard.InvokeExtensions.Invoke[TEvents](IEnumerable`1 events, Action`1 dispatch, ILogger logger) â d:\TeamCity\Projects\Orchard-Default\src\Orchard\InvokeExtensions.cs:ñòðîêà 19
EDIT: some model classes:
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class AreaRecord : ContentPartRecord {
public virtual string Name { get; set; }
public virtual DistrictRecord DistrictRecord { get; set; }
}
public class AreaPart : ContentPart<AreaRecord> {
[Required]
public string Name {
get { return Record.Name; }
set { Record.Name = value; }
}
[Required]
public DistrictRecord DistrictRecord {
get { return Record.DistrictRecord; }
set { Record.DistrictRecord = value; }
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class StreetRecord : ContentPartRecord {
public virtual string Name { get; set; }
public virtual IList<StreetAreaRecord> Areas { get; set; }
public StreetRecord() {
Areas = new List<StreetAreaRecord>();
}
}
public class StreetPart : ContentPart<StreetRecord> {
[Required]
public string Name {
get { return Record.Name; }
set { Record.Name = value; }
}
public IEnumerable<AreaRecord> Areas {
get {
return Record.Areas.Select (r => r.AreaRecord);
}
}
public IEnumerable<DistrictRecord> Districts {
get {
return Record.Areas.Select (r => r.AreaRecord.DistrictRecord).Distinct();
}
}
}
}
using System.ComponentModel.DataAnnotations;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;
namespace ArealAds.Models {
public class StreetAreaRecord : ContentPartRecord {
public virtual StreetRecord StreetRecord { get; set; }
public virtual AreaRecord AreaRecord { get; set; }
}
}
using ArealAds.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
namespace ArealAds.Handlers {
public class AreaHandler : ContentHandler {
public AreaHandler(IRepository<AreaRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}
using ArealAds.Models;
using Orchard.ContentManagement.Handlers;
using Orchard.Data;
namespace ArealAds.Handlers {
public class StreetHandler : ContentHandler {
public StreetHandler(IRepository<StreetRecord> repository) {
Filters.Add(StorageFilter.For(repository));
}
}
}
Your StreetAreaRecord model is missing an Id property. Since it's not a ContentPartRecord, you have to set up the property manually.
public virtual int Id { get; set; }
The prefix is to ensure a unique id attribute in the html fields in the part editor. You might have multiple parts within a single content type with a "Name" field. Without the prefix the html would be invalid and postback wouldn't work because there would be two fields with id="Name". You can simply set the prefix to be the name of the part.
I'm not sure the prefix is what's preventing your part from saving. Have you checked the Handler for your part? Make sure it sets up the filter for the StreetPartRecord, that is often the cause of a new Part not saving on postback.

How to modify ILookup object in C# 4.0?

Before .NET 3.5 was released, I use
Dictionary<TKey, List<TValue>>
for containing data. But I just found that .NET 3.5 provides new collection type that is ILookup class that can represent my old complex data type.
I always create ILookup object by using LINQ extension method (ToLookup method). But I do not know how to modify ILookup object.
Is it possible? Or I need to create by using union method and call ToLookup method again.
Thanks,
You don't, it's immutable. You have listed both of the reasonable options; either to use a dictionary of sub-collections or to keep creating new lookups.
Here is an example of an implementation of ILookup that can be manipulated. It wraps around a Dictionary of List's of elements. It is completely generic. I couldn't think of a better name. :)
public class LookupDictionary<TKey, TElement> : ILookup<TKey, TElement>
{
private Dictionary<TKey, List<TElement>> _dicLookup = new Dictionary<TKey, List<TElement>>();
public LookupDictionary()
{
}
public LookupDictionary(ILookup<TKey, TElement> a_lookup)
{
foreach (var grouping in a_lookup)
{
foreach (var element in grouping)
AddElement(grouping.Key, element);
}
}
public IEnumerable<TElement> AllElements
{
get
{
return (from key in _dicLookup.Keys
select _dicLookup[key])
.SelectMany(list => list);
}
}
public int Count
{
get
{
return AllElements.Count();
}
}
public IEnumerable<TElement> this[TKey a_key]
{
get
{
List<TElement> list;
if (_dicLookup.TryGetValue(a_key, out list))
return list;
return new TElement[0];
}
}
public bool Contains(TKey a_key)
{
return _dicLookup.ContainsKey(a_key);
}
public void Add(TKey a_key, TElement a_element)
{
AddElement(a_key, a_element);
}
public void RemoveKey(TKey a_key)
{
_dicLookup.Remove(a_key);
}
public IEnumerator<IGrouping<TKey, TElement>> GetEnumerator()
{
return GetGroupings().GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (GetGroupings() as System.Collections.IEnumerable).GetEnumerator();
}
private void AddElement(TKey a_key, TElement a_element)
{
List<TElement> list;
if (!_dicLookup.TryGetValue(a_key, out list))
{
list = new List<TElement>();
_dicLookup.Add(a_key, list);
}
list.Add(a_element);
}
private IEnumerable<IGrouping<TKey, TElement>> GetGroupings()
{
return from key in _dicLookup.Keys
select new LookupDictionaryGrouping<TKey, TElement>
{
Key = key,
Elements = _dicLookup[key]
} as IGrouping<TKey, TElement>;
}
}
public class LookupDictionaryGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
public TKey Key
{
get;
set;
}
public IEnumerable<TElement> Elements
{
get;
set;
}
public IEnumerator<TElement> GetEnumerator()
{
return Elements.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return (Elements as System.Collections.IEnumerable).GetEnumerator();
}
}
As mquander mentioned, the lookup is immutable. However, you can build a new lookup with additional or removed values.
// Add a new value
myLookup = myLookup
.SelectMany(l => l.Select(v => new {l.Key, Value = v}))
.Union(new[] {new {Key = myNewKey, Value = myNewValue}})
.ToLookup(a => a.Key, a => a.Value);
// Remove an old value
myLookup = myLookup
.SelectMany(l => l.Select(v => new {l.Key, Value = v}))
.Where(a => a.Value != myOldValue)
.ToLookup(a => a.Key, a => a.Value);

Resources