{"id":23050826,"url":"https://github.com/geta/geta-optimizely-productfeed","last_synced_at":"2026-03-06T15:03:41.007Z","repository":{"id":38238407,"uuid":"367350633","full_name":"Geta/geta-optimizely-productfeed","owner":"Geta","description":"Product Feed for Optimizely Commerce. Supports custom CSV and Google Product Feed formats.","archived":false,"fork":false,"pushed_at":"2025-02-26T07:16:11.000Z","size":238712,"stargazers_count":2,"open_issues_count":6,"forks_count":1,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-29T18:14:18.070Z","etag":null,"topics":["commerce","csv","googleproductfeed","optimizely","productfeed","xml"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Geta.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-05-14T12:07:08.000Z","updated_at":"2025-02-26T07:16:14.000Z","dependencies_parsed_at":"2025-04-24T04:45:43.788Z","dependency_job_id":null,"html_url":"https://github.com/Geta/geta-optimizely-productfeed","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/Geta/geta-optimizely-productfeed","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-optimizely-productfeed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-optimizely-productfeed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-optimizely-productfeed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-optimizely-productfeed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Geta","download_url":"https://codeload.github.com/Geta/geta-optimizely-productfeed/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2Fgeta-optimizely-productfeed/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30182686,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T14:42:24.748Z","status":"ssl_error","status_checked_at":"2026-03-06T14:42:14.925Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["commerce","csv","googleproductfeed","optimizely","productfeed","xml"],"created_at":"2024-12-15T23:38:13.420Z","updated_at":"2026-03-06T15:03:40.968Z","avatar_url":"https://github.com/Geta.png","language":"C#","readme":"# Geta Optimizely Product Feed\n\n## Status\n\n![](https://tc.geta.no/app/rest/builds/buildType:(id:GetaPackages_OptimizelyGoogleProductFeed_00ci),branch:master/statusIcon)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Geta_geta-optimizely-productfeed\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Geta_geta-optimizely-productfeed)\n[![Platform](https://img.shields.io/badge/Platform-.NET%208-blue.svg?style=flat)](https://docs.microsoft.com/en-us/dotnet/)\n[![Platform](https://img.shields.io/badge/Optimizely-%2012-orange.svg?style=flat)](http://world.episerver.com/cms/)\n[![Platform](https://img.shields.io/badge/Optimizely%20Commerce-14.30-orange.svg?style=flat)](http://world.episerver.com/commerce/)\n\nCredits: [How to make a Google Shopping Feed with C# and serve it through the Web API](http://blog.codenamed.nl/2015/05/14/creating-a-google-shopping-feed-with-c/).\n\nAdd-on will create a Google Product Feed based on the [Atom specification](https://support.google.com/merchants/answer/160593?hl=en). For information on what is required and what the different attributes/properties mean, please see the [Product data specification](https://support.google.com/merchants/answer/188494).\n\n## Installation\n\n```\n\u003e dotnet add package Geta.Optimizely.ProductFeed\n\u003e dotnet add package Geta.Optimizely.ProductFeed.Google\n\n# and/or\n\n\u003e dotnet add package Geta.Optimizely.ProductFeed.Csv\n```\n\n## Configuration\n\nFor the `ProductFeed` package to work, you have to call `AddProductFeed\u003cT\u003e()` and `AddGoogleXmlExport()` extension methods in `Startup.ConfigureServices` method. Using configuration options parameter of this method, you can provide a DB connection string and other setup information.\n\n```csharp\nservices\n    .AddProductFeed\u003cMyCommerceProductRecord\u003e(x =\u003e\n    {\n        x.ConnectionString = _configuration.GetConnectionString(\"EPiServerDB\");\n        x.SetEntityMapper\u003cEntityMapper\u003e();\n    });\n```\n\nHere `MyCommerceProductRecord` is an entity to which a generic `CatalogContentBase` objects will be mapped to.\n\nThen you need to add mapping to serve feeds from configured URLs:\n\n```csharp\npublic void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n{\n    app.UseEndpoints(endpoints =\u003e\n    {\n        ...\n        endpoints.MapProductFeeds();\n    });\n}\n```\n\n## Processing Pipeline\n\nDuring the processing pipeline there are a few key moments to be aware of (you can override any of these mentioned below):\n\n* Catalog Data **Load** - loads data from the Optimizely Commerce catalog.\n* Catalog Data **Map** - loaded data usually comes in `CatalogContentBase` shape. This step allows mapping to `T` data type (mentioned in `AddProductFeed\u003cT\u003e()` method).\n* Entity **Filters** - filters are deciding whether entity should be included in the processing pipeline or filtered out.\n* **Enrichments** - When loaded data is mapped to a custom entity, the processing pipeline can start the work. Enrichments are responsible for loading some heavy data and adding necessary metadata to the `T` entity.\n* Feed **Exporters** - exporters are responsible for generating feed content in a specific format and using specific feed entities.\n* Feed **Filters** - filters are responsible for deciding whether specific entity should be included in the feed.\n* Feed entity **Converter** - converters are responsible for taking the projected entity (`T` mentioned in `AddProductFeed()`) and return a feed entity which will be used to store the actual product feed in the underlying storage.\n* **Storage** Providers - right now we only have MSSQL storage implementation. But should be quite easy to implement the next one.\n\n![](docs/images/productfeed-1.png)\n\n### Adding Google Xml Export\n\nFollowing code adds Google Xml product feed functionality to your site:\n\n```\nservices\n    .AddProductFeed\u003cMyCommerceProductRecord\u003e(options =\u003e\n    {\n        options.ConnectionString = _configuration.GetConnectionString(\"EPiServerDB\");\n        options.SetEntityMapper\u003cEntityMapper\u003e();\n\n        options.SetFilter\u003cGenericEntityFilter\u003e();\n        options.AddEnricher\u003cFashionProductAvailabilityEnricher\u003e();\n\n        options.AddGoogleXmlExport(d =\u003e\n        {\n            d.FileName = \"/google-feed\";\n            d.SetFilter\u003cGoogleXmlFilter\u003e();\n            d.SetConverter\u003cGoogleXmlConverter\u003e();\n        });\n    });\n```\n\nFew notes:\n\n* Loaded commerce catalog data will be mapped to `MyCommerceProductRecord` class.\n* `EntityMapper` will be used to do this mapping.\n* `FashionProductAvailabilityEnricher` will be used to process each `MyCommerceProductRecord` and set SKU availability by some criteria.\n* `GoogleXmlFilter` filter will be used for each entity to decide whether particular entity should be included in the feed.\n* Google Xml product feed entities will be generated using `GoogleXmlConverter` class.\n* Feed data will be stored in MSSQL database under `\"EPiServerDB\"` connection string.\n* Google Xml product feed will be mounted to `/google-feed` URL.\n\nCustom mapped entity class (`MyCommerceProductRecord.cs`):\n\n```csharp\npublic class MyCommerceProductRecord\n{\n    public string Code { get; set; }\n    public string DisplayName { get; set; }\n    public string Description { get; set; }\n    public string Url { get; set; }\n    public string Brand { get; set; }\n    public string ImageLink { get; set; }\n    public bool IsAvailable { get; set; }\n}\n```\n\nCustom entity mapper (`EntityMapper.cs`):\n\n```csharp\npublic class EntityMapper : IEntityMapper\u003cMyCommerceProductRecord\u003e\n{\n    public MyCommerceProductRecord Map(CatalogContentBase catalogConte\n    {\n        return ...\n    }\n}\n```\n\nGeneric converted entity filter (`GenericEntityFilter.cs`):\n\n```csharp\npublic class GenericEntityFilter : IProductFeedFilter\u003cMyCommerceProductRecord\u003e\n{\n    public bool ShouldInclude(MyCommerceProductRecord entity)\n    {\n        // filter out ALL entities from the feed\n        return false;\n    }\n}\n```\n\nEnricher (`FashionProductAvailabilityEnricher.cs`):\n\n```csharp\npublic class FashionProductAvailabilityEnricher :\n    IProductFeedContentEnricher\u003cMyCommerceProductRecord\u003e\n{\n    public MyCommerceProductRecord Enrich(\n        MyCommerceProductRecord sourceData,\n        CancellationToken cancellationToken)\n    {\n        // enrich SKU availability\n    }\n}\n```\n\nGoogle Xml feed filter (`GoogleXmlFilter.cs`):\n\n```csharp\npublic class GoogleXmlFilter : IProductFeedFilter\u003cMyCommerceProductRecord\u003e\n{\n    public bool ShouldInclude(MyCommerceProductRecord entity)\n    {\n        return true;\n    }\n}\n```\n\nGoogle Xml feed entity converter (`GoogleXmlConverter.cs`):\n\n```csharp\npublic class GoogleXmlConverter : IProductFeedConverter\u003cMyCommerceProductRecord\u003e\n{\n    public object Convert(MyCommerceProductRecord entity, HostDefinition host)\n    {\n        return new Geta.Optimizely.ProductFeed.Google.Models.Entry\n        {\n            // set properties for feed entity\n        }\n    }\n}\n```\n\n### Adding CSV Export\n\nAdding CSV export is quite easy as well.\n\nThen add somewhat similar code to your `Startup.cs` file:\n\n```csharp\nservices\n    .AddProductFeed\u003cMyCommerceProductRecord\u003e(options =\u003e\n    {\n        options.ConnectionString = _configuration.GetConnectionString(\"EPiServerDB\");\n        options.SetEntityMapper\u003cEntityMapper\u003e();\n\n        options.AddEnricher\u003cFashionProductAvailabilityEnricher\u003e();\n\n        options.AddCsvExport(d =\u003e\n        {\n            d.FileName = \"/csv-feed-1\";\n            d.SetConverter\u003cCsvConverter\u003e();\n            d.CsvEntityType = typeof(CsvEntry);\n        });\n    });\n```\n\nAll processing pipeline logic is the same as for the Google Xml feed except following changes:\n\n* CSV product feed entity is set to `CsvEntity`.\n* Feed entity is converted via `CsvConverter`.\n* Feed is mounted to `/csv-feed-1` route.\n\nCustom CSV entity (`CsvEntity.cs`):\n\n```csahrp\npublic class CsvEntry\n{\n    public string Name { get; set; }\n    public string Code { get; set; }\n    public decimal Price { get; set; }\n    public bool IsAvailable { get; set; }\n}\n```\n\nCSV converter (`CsvConverter.cs`):\n\n```csharp\npublic class CsvConverter : IProductFeedConverter\u003cMyCommerceProductRecord\u003e\n{\n    public object Convert(MyCommerceProductRecord entity, HostDefinition host)\n    {\n        return new CsvEntry\n        {\n            // set properties for the entity\n        };\n    }\n}\n```\n\nCSV feed will have a header row generated out-of-the-box based on properties in `CsvEntity` class.\n\n## Feed Generation\n\nPopulating the feed is handled through a scheduled job and the result is serialized and stored in the database. See job `ProductFeed - Create feeds` in the Optimizely Admin mode.\n\n## Troubleshooting\n\nIf your request to `/googleproductfeed` (or any other path that you configured for the feed) returns 404 with message `No feed generated`, make sure you run the job to populate the feed.\n\n## 🏁 Getting Started\n\n### 📦 Prerequisites\n\nEnsure your system is properly configured to meet all prerequisites for Geta Foundation Core listed [here](https://github.com/Geta/geta-foundation-core#%EF%B8%8F-prerequisites)\n\n### 🐑 Cloning the repository\n\n```bash\n    git clone https://github.com/Geta/geta-optimizely-productfeed.git\n    cd geta-optimizely-productfeed\n    git submodule update --init\n```\n\n### 🚀 Running with Aspire (Recommended)\n```bash\n    # Windows\n    cd sub/geta-foundation-core/src/Foundation.AppHost\n    dotnet run\n\n    # Linux / MacOS\n    sudo env \"PATH=$PATH\" bash\n    chmod +x sub/geta-foundation-core/src/Foundation/docker/build-script/*.sh\n    cd sub/geta-foundation-core/src/Foundation.AppHost\n    dotnet run\n```\n\n### 🖥️ Running as Standalone\n```bash\n   # Windows\n   cd sub/geta-foundation-core\n   ./setup.cmd\n   cd ../../src/Geta.Optimizely.ProductFeed.Web\n   dotnet run\n\n   # Linux / MacOS\n   sudo env \"PATH=$PATH\" bash\n   cd sub/geta-foundation-core\n   chmod +x *.sh\n   ./setup.sh\n   cd ../../src/Geta.Optimizely.ProductFeed.Web\n   dotnet run\n```\n\nIf you run into any issues, check the FAQ section [here](https://github.com/Geta/geta-foundation-web?tab=readme-ov-file#faq) \n\n---\n\nCMS username: admin@example.com\n\nPassword: Episerver123!\n\n\n## Package maintainer\n\nhttps://github.com/valdisiljuconoks\n\n## Changelog\n\n[Changelog](CHANGELOG.md)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fgeta-optimizely-productfeed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeta%2Fgeta-optimizely-productfeed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fgeta-optimizely-productfeed/lists"}