{"id":23050861,"url":"https://github.com/geta/googleproductfeed","last_synced_at":"2025-08-15T03:31:42.594Z","repository":{"id":40909594,"uuid":"72049616","full_name":"Geta/GoogleProductFeed","owner":"Geta","description":null,"archived":false,"fork":false,"pushed_at":"2022-08-31T23:41:30.000Z","size":112553,"stargazers_count":6,"open_issues_count":29,"forks_count":3,"subscribers_count":27,"default_branch":"master","last_synced_at":"2023-04-10T16:50:44.126Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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}},"created_at":"2016-10-26T22:25:33.000Z","updated_at":"2022-03-21T10:14:06.000Z","dependencies_parsed_at":"2023-01-17T01:45:45.126Z","dependency_job_id":null,"html_url":"https://github.com/Geta/GoogleProductFeed","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FGoogleProductFeed","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FGoogleProductFeed/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FGoogleProductFeed/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Geta%2FGoogleProductFeed/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Geta","download_url":"https://codeload.github.com/Geta/GoogleProductFeed/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229890100,"owners_count":18140042,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-12-15T23:38:57.830Z","updated_at":"2024-12-15T23:38:58.426Z","avatar_url":"https://github.com/Geta.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Geta Google Product Feed\n\n* Master\u003cbr\u003e\n![](http://tc.geta.no/app/rest/builds/buildType:(id:GetaPackages_GetaGoogleProductFeed_00ci),branch:master/statusIcon)\n[![Platform](https://img.shields.io/badge/Platform-.NET%204.6.1-blue.svg?style=flat)](https://msdn.microsoft.com/en-us/library/w0x726c2%28v=vs.110%29.aspx)\n[![Platform](https://img.shields.io/badge/Episerver-%2011-orange.svg?style=flat)](http://world.episerver.com/cms/)\n[![Platform](https://img.shields.io/badge/Episerver%20Commerce-13-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\nThis 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```\nInstall-Package Geta.GoogleProductFeed\n```\n\nNote that you need to make sure your projects calls config.MapHttpAttributeRoutes(); in order for the feed routing to work.\n\nDefault URL is: /googleproductfeed\n\n## FeedBuilder\nYou need to implement the abstract class FeedBuilder and the method Build. This will provide the feed data. Build method returns List of feeds, this is required so that FeedBuilder can produce feeds for both multisite and singlesite projects. Example bellow can be extended to support multisite projects.\n\n### Default FeedBuilder\nYou can iherit from default base feed builder class (`DefaultFeedBuilderBase`) which will help you get started.\nIt contains `CatalogEntry` enumeration code and sample error handling. You will need to implement following methods:\n\n```csharp\nprotected abstract Feed GenerateFeedEntity();\n\nprotected abstract Entry GenerateEntry(CatalogContentBase catalogContent);\n```\n\nFor example:\n\n```csharp\npublic class EpiFeedBuilder : DefaultFeedBuilderBase\n{\n    private readonly IPricingService _pricingService;\n    private readonly Uri _siteUri;\n\n    public EpiFeedBuilder(\n        IContentLoader contentLoader,\n        ReferenceConverter referenceConverter,\n        IPricingService pricingService,\n        ISiteDefinitionRepository siteDefinitionRepository,\n        IContentLanguageAccessor languageAccessor) : base(contentLoader, referenceConverter, languageAccessor)\n    {\n        _pricingService = pricingService;\n        _siteUri = siteDefinitionRepository.List().FirstOrDefault()?.Hosts.GetPrimaryHostDefinition().Url;\n    }\n\n    protected override Feed GenerateFeedEntity()\n    {\n        return new Feed\n        {\n            Updated = DateTime.UtcNow,\n            Title = \"My products\",\n            Link = _siteUri.ToString()\n        };\n    }\n\n    protected override Entry GenerateEntry(CatalogContentBase catalogContent)\n    {\n        return ...;\n    }\n\n    private HostDefinition GetPrimaryHostDefinition(IList\u003cHostDefinition\u003e hosts)\n    {\n        if (hosts == null)\n        {\n            throw new ArgumentNullException(nameof(hosts));\n        }\n\n        return hosts.FirstOrDefault(h =\u003e h.Type == HostDefinitionType.Primary \u0026\u0026 !h.IsWildcardHost())\n               ?? hosts.FirstOrDefault(h =\u003e !h.IsWildcardHost());\n    }\n}\n```\n\n### Implement Your Own Builder\nIf you need more flexible solution to build Google Product Feed - you can implement whole builder yourself.\nBelow is given sample feed builder (based on Quicksilver demo project). Please use it as starting point and adjust things that you need to customize.\nAlso keep in mind that for example error handling is not implemented in this sample (which means if variation generation fails - job will be aborted and feed will not be generated at all).\n\n```csharp\npublic class EpiFeedBuilder : FeedBuilder\n{\n    private readonly IContentLoader _contentLoader;\n    private readonly ReferenceConverter _referenceConverter;\n    private readonly IPricingService _pricingService;\n    private readonly ILogger _logger;\n    private readonly IContentLanguageAccessor _languageAccessor;\n    private readonly Uri _siteUri;\n\n    public EpiFeedBuilder(\n        IContentLoader contentLoader,\n        ReferenceConverter referenceConverter,\n        IPricingService pricingService,\n        ISiteDefinitionRepository siteDefinitionRepository,\n        IContentLanguageAccessor languageAccessor)\n    {\n        _contentLoader = contentLoader;\n        _referenceConverter = referenceConverter;\n        _pricingService = pricingService;\n        _logger = LogManager.GetLogger(typeof(EpiFeedBuilder));\n        _languageAccessor = languageAccessor;\n        _siteUri = GetPrimaryHostDefinition(siteDefinitionRepository.List().FirstOrDefault()?.Hosts)?.Url;\n    }\n\n    public override List\u003cFeed\u003e Build()\n    {\n        List\u003cFeed\u003e generatedFeeds = new List\u003cFeed\u003e();\n        Feed feed = new Feed\n        {\n            Updated = DateTime.UtcNow,\n            Title = \"My products\",\n            Link = _siteUri.ToString()\n        };\n\n        IEnumerable\u003cContentReference\u003e catalogReferences = _contentLoader.GetDescendents(_referenceConverter.GetRootLink());\n        IEnumerable\u003cCatalogContentBase\u003e items = _contentLoader.GetItems(catalogReferences, CreateDefaultLoadOption()).OfType\u003cCatalogContentBase\u003e();\n\n        List\u003cEntry\u003e entries = new List\u003cEntry\u003e();\n        foreach (CatalogContentBase catalogContent in items)\n        {\n            FashionVariant variationContent = catalogContent as FashionVariant;\n\n            try\n            {\n                if (variationContent == null)\n                    continue;\n\n                FashionProduct product = _contentLoader.Get\u003cCatalogContentBase\u003e(variationContent.GetParentProducts().FirstOrDefault()) as FashionProduct;\n                string variantCode = variationContent.Code;\n                IPriceValue defaultPrice = _pricingService.GetPrice(variantCode);\n\n                Entry entry = new Entry\n                {\n                    Id = variationContent.Code,\n                    Title = variationContent.DisplayName,\n                    Description = product?.Description.ToHtmlString(),\n                    Link = variationContent.GetUrl(),\n                    Condition = \"new\",\n                    Availability = \"in stock\",\n                    Brand = product?.Brand,\n                    MPN = \"\",\n                    GTIN = \"...\",\n                    GoogleProductCategory = \"\",\n                    Shipping = new List\u003cShipping\u003e\n                    {\n                        new Shipping\n                        {\n                            Price = \"Free\",\n                            Country = \"US\",\n                            Service = \"Standard\"\n                        }\n                    }\n                };\n\n                string image = variationContent.GetDefaultAsset\u003cIContentImage\u003e();\n\n                if (!string.IsNullOrEmpty(image))\n                {\n                    entry.ImageLink = Uri.TryCreate(_siteUri, image, out Uri imageUri) ? imageUri.ToString() : image;\n                }\n\n                if (defaultPrice != null)\n                {\n                    IPriceValue discountPrice = _pricingService.GetDiscountPrice(variantCode);\n\n                    entry.Price = defaultPrice.UnitPrice.ToString();\n                    entry.SalePrice = discountPrice.ToString();\n                    entry.SalePriceEffectiveDate = $\"{DateTime.UtcNow:yyyy-MM-ddThh:mm:ss}/{DateTime.UtcNow.AddDays(7):yyyy-MM-ddThh:mm:ss}\";\n                }\n\n                entries.Add(entry);\n            }\n            catch (Exception ex)\n            {\n                _logger.Error($\"Failed to generate feed item for catalog entry ({catalogContent.ContentGuid})\", ex);\n            }\n        }\n\n        feed.Entries = entries;\n        generatedFeeds.Add(feed);\n\n        return generatedFeeds;\n    }\n\n    private HostDefinition GetPrimaryHostDefinition(IList\u003cHostDefinition\u003e hosts)\n    {\n        if (hosts == null)\n        {\n            throw new ArgumentNullException(nameof(hosts));\n        }\n\n        return hosts.FirstOrDefault(h =\u003e h.Type == HostDefinitionType.Primary \u0026\u0026 !h.IsWildcardHost())\n                ?? hosts.FirstOrDefault(h =\u003e !h.IsWildcardHost());\n    }\n\n    private LoaderOptions CreateDefaultLoadOption()\n    {\n        LoaderOptions loaderOptions = new LoaderOptions\n        {\n            LanguageLoaderOption.FallbackWithMaster(_languageAccessor.Language)\n        };\n\n        return loaderOptions;\n    }\n}\n```\n\n\n### Register Builder in IoC\nThen you need to use this as the default implementation for FeedBuilder. Using StructureMap it will look something like this in your registry class:\n\n```csharp\nFor\u003cFeedBuilder\u003e().Use\u003cEpiFeedBuilder\u003e();\n```\n\nMake sure dependency injection is setup for Web API. The quickest way to do this is install the package: Foundation.WebApi.\n\n## Feed Generation\nPopulating the feed is handled through a scheduled job and the result is serialized and stored in the database. See job `Google ProductFeed - Create feed` in admin mode. \n\n## Troubleshooting\nIf your request to `/googleproductfeed` returns 404 with message `No feed generated`, make sure you run the job to populate the feed.\n\n## Local development setup\nSee description in [shared repository](https://github.com/Geta/package-shared/blob/master/README.md#local-development-set-up) regarding how to setup local development environment.\n\n### Docker hostnames\nInstead of using the static IP addresses the following hostnames can be used out-of-the-box.\n\nhttp://googleproductfeed.getalocaltest.me\nhttp://manager-googleproductfeed.getalocaltest.me\n\n\n## Package maintainer\nhttps://github.com/valdisiljuconoks\n\n## Changelog\n[Changelog](CHANGELOG.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fgoogleproductfeed","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeta%2Fgoogleproductfeed","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeta%2Fgoogleproductfeed/lists"}