By default, the orders numbers are generated by an instance of EPiServer.Commerce.Order.IOrderNumberGenerator.
The default number generation is:
public string GenerateOrderNumber(IOrderGroup orderGroup)
{
int num = new Random().Next(100, 999);
return $"PO{orderGroup.OrderLink.OrderGroupId}{num}";
}
So, to change this, first we need to inject a custom implementation and remove the default one. On an initialization module that depends on EPiServer.Web.InitializationModule have the following:
Scenario: Wanted to add a new schema, table and stored procedure to the same database where CMS is installed. The script should run only once after deployment to an environment.
Using IMigrationSet can do this job, although is different from a Initialization Module that run every time the site starts up.
Step 1. Create a folder in your project that is referenced by Episerver CMS website.
Step 2. Create two files: a class and sql script. For exemple:
Step 3. Define the content for you sql script. For good practices, we must use a different schema from the dbo where all the Episerver objects are defined. So, we need to create the following script :
--beginvalidatingquery
IF OBJECT_ID(N'[myschema].[myTable]', N'U') IS NOT NULL
SELECT 0, 'Already installed'
else
SELECT 1, 'Adding tables'
--endvalidatingquery
go
create schema [myschema]
go
create table [ownschema].[myTable]
(
[Id] [bigint] IDENTITY(1,1) not null,
[Text] [nvarchar](200) not null,
constraint [PK_myTable] primary key ([Id])
)
go
Step 4. Mark you script file as Embedded Resource.
Step 5. Define your migration step on your c# class.
using EPiServer.Commerce.Internal.Migration.Steps;
using EPiServer.Data.Providers.Internal;
using EPiServer.Data.SchemaUpdates;
using EPiServer.Logging;
using EPiServer.ServiceLocation;
using Mediachase.Commerce.Shared;
using System;
using System.IO;
using System.Reflection;
namespace Common.Migrations.CreateTablesStoredProcedures
{
[ServiceConfiguration(typeof(IMigrationStep))]
public class CreateTablesAndStoredProceduresMigrationStep : IMigrationStep
{
private static readonly ILogger Logger =
LogManager.GetLogger(typeof(CreateTablesAndStoredProceduresMigrationStep));
private readonly IDatabaseConnectionResolver _databaseConnectionResolver;
private readonly ScriptExecutor _scriptExecutor;
public CreateTablesAndStoredProceduresMigrationStep(
IDatabaseConnectionResolver databaseConnectionResolver,
ScriptExecutor scriptExecutor)
{
_databaseConnectionResolver = databaseConnectionResolver;
_scriptExecutor = scriptExecutor;
}
public int Order => 2000;
public string Name => "Create Tables And StoredProcedures";
public string Description => "Create Tables and Stored Procedures using Embedded Resource sql file ";
public bool Execute(IProgressMessenger progressMessenger)
{
try
{
using (Stream installScript = GetInstallScript())
{
_scriptExecutor.ExecuteScript(
_databaseConnectionResolver.Resolve().ConnectionString,
installScript);
}
return true;
}
catch (Exception ex)
{
Logger.Error("Error occurred while creating the tables and stored procedures.", ex);
return false;
}
}
private static Stream GetInstallScript()
{
const string scriptFqdn = "Common.Migrations.CreateTablesAndStoredProceduresMigrationStep.script.sql";
Assembly assembly = typeof(CreateTablesAndStoredProceduresMigrationStep).Assembly;
return assembly.GetManifestResourceStream(scriptFqdn);
}
}
}
Step 6. Deploy.
Note that this migration step will run only once if no exception occurred, this is, if the return value of the Execute method is true. If returned false, this migration step will be executed next time the site started.
An workaround on development mode to repeat this migration is always return false, but ensure that this does not go to production.
I got the case, that need to repeat this step, but already returned success. Used a not documented and not official way of removing the success result of this migration by deleting the row from [dbo].[tblBigTable]. I just found it during my tests, just warning you. So, I advise to this only on you local development database.
SELECT pkId, String01 as [Name]
FROM [dbo].[tblBigTable] where StoreName = 'EPiServer.Commerce.Internal.Migration.MigrationStepInfo'
and [String01] = 'Common.Migrations.CreateTablesAndStoredProceduresMigrationStep'
delete [dbo].[tblBigTable] where StoreName = 'EPiServer.Commerce.Internal.Migration.MigrationStepInfo'
and [String01] = 'Common.Migrations.CreateTablesAndStoredProceduresMigrationStep'
Even with access rights correctly in place, the “For all sites” folder did not appear. So, went to the Website settings at Admin, and remove the check on “Use site-specifics assets”, save and recheck again to true.
It worked for me, and the images appeared again 😉
Suppose the front-end code wants supply a way to download images from the assets folder. Consider the following code:
<a href="http://127.0.0.1:5500/globalassets/images/sample.png" download="sample.png">Download this file</a>
Assuming that 127.0.0.1:5500 is your EPiServer instance, you can say that the image will be downloaded.
Note that download anchor attribute will work if the url is from the same origin of the http request.
For Internet Explorer, the download attribute is not supported, so the image is open on the same tab.
We want to ensure that the response header Content-Disposition: attachment; filename="sample.png"; filename*=UTF-8''sample.png is returned, so each browser saves the image instead of present it on the same tab.
Using the DownloadMediaRouter.DownloadSegment on the request, EPiServer will return the image with the Content-Disposition with attachment value.
So, we can replace the html anchor definition by:
<a href="http://127.0.0.1:5500/globalassets/images/sample.png/download">Download this file</a>
I need to add extra information on each LineItem of the order. But also to see it on the OrderSummary view on the Commerce UI. Please comment if i can implement a better solution than following approach, because i thought that it could be a fast solution by changing the xml file were we define the order ui like PurchaseOrder-ObjectView.xml
First, defined the Meta Fields and associated with the LineItemEx:
When creating the LineItem, don’t forget to had the new attribute the Properties collection:
Change the CommerceManager/Apps/Order/Modules/OrderSummary.ascx file in order to define a new code behind and add a new BoundColumn with DataField=”ReasonCode” that will bind the new MetaField
Add to the solution a new OrderSummary.ascx.cs file with the same code has the episerver OrderSummary.ascx.cs existing file. Used ILSpy to get the code from Mediachase.Commerce.Manager.Apps.Order.Modules.OrderSummary.
On the private method BindData, change DataTable colums’ definition to include the new MetaField and the rows added to include LineItem.Properties[New MetaField Name] value.
Consider the following scenario: Developing with debug compilation and using IIS Express. Everything works fine without license. Episerver allows development under those circumstances.
But if you deploy to a new IIS site or even to Azure web app and you set compilation without debug, because you probably published with Release configuration:
With this we start to create the CMS web application
Choose empty template:
And the simple site folders and files are created with default values.
Lets update the nuget packages by running the following command at the Package Manager Console:
Update-Package EPiServer.CMS -ToHighestMinor
Do the same for the EPiServer.CMS.UI and EPiServer.CMS.TinyMce packages
Using package manage console and selecting only ToHighestMinor allows Episerver packages to be safely installed because it won’t upgrade to a higher major version that would have breaking changes, and non-Episerver dependencies will be updated, as well as the Episerver ones but it won’t accidently install newer but incompatible packages.
By default, this site is using LocalDb database files for demo purpose. Lets create two database using SQLExpress and SQL Server Management Studio.
Create epidemo.cms and epidemo.commerce database.
Now, it’s time to change the connections string to set the new database. For your new epidemo.cms database we can use the following:
You may ask if this is necessary for the post purpose, no its not, but i think is always good to learn something else if you did not know how to split the web.config in multiple files.
Now we have conditions to initialize your database, for now it’s only an empty database:
At Visual Studio Package Manager Console, execute the following command:
initialize-epidatabase
Now, the database was populated with necessary for support Episerver CMS.
After initialize the database, its a good idea to update to the latest changes, by executing the following:
update-epidatabase
In this demo, i don’t want to use Windows Integrated authentication, that is set by default. I would like to use SQL database to store the users.
If you look at the tables that were created at database, there is no support for managing the users. So, let’s change the default provider for membership and roleManager. Find those section at web config and change the defaultProvider to SqlServerMembershipProvider and SqlServerRoleProvider.
With this we use Membership. On another post i will change this to use Identity. Here is a tutorial to use Identity
Since we don’t have yet a user created, we can use an initialization module to create a administrator user with the respective role. So, on Business folder create a new folder named Initialization and a class named RegisterAdminInitializationModule
Add the following code to that class:
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using System.Configuration;
using System.Web.Security;
namespace EpiserverDemo.CMS.Business.Initialization
{
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class RegisterAdminInitializationModule : IInitializableModule
{
private const string roleName = "Administrators";
private const string userName = "admin";
private const string password = "store";
private const string email = "admin@EpiserverAdventures.com";
public void Initialize(InitializationEngine context)
{
string enabledString =
ConfigurationManager.AppSettings["EpiserverAdventures:RegisterAdmin"];
bool enabled;
if (bool.TryParse(enabledString, out enabled))
{
if (enabled)
{
#region Use ASP.NET Membership classes to create the role and user
// if the role does not exist, create it
if (!Roles.RoleExists(roleName))
{
Roles.CreateRole(roleName);
}
// if the user already exists, delete it
MembershipUser user = Membership.GetUser(userName);
if (user != null)
{
Membership.DeleteUser(userName);
}
// create the user with password and add it to role
Membership.CreateUser(userName, password, email);
Roles.AddUserToRole(userName, roleName);
#endregion
}
}
}
public void Uninitialize(InitializationEngine context) { }
}
}
Change the Web.Config by adding the following to appSettings:
Now its time to create the new backend site for the Ecommerce.
Create a new empty asp.net web application:
Add to the EpiserverDemo.Commerce project the nuget package Episerver.CommerceManager
Dont cancel the copy of files
For some reason, the project file was not updated:
Include all files into the project, except obj and bin folders
Also add the nuget package EPiServer.ServiceLocation.StructureMap to the Commerce Project.
Copy from the CMS project the connectionString.config into the Commerce project and replace on the Web.Config (Commerce project) the connectionString section with:
When creating new types instances, such as pages or blocks, we get a lot of help from the CMS UI identifying which type we want.
But, to offer better experience to the editors, it would be much nice that these types could be present with an image.
Episerver recomend 120×90 dimention for each icon, but if you give different it will be fit to that.
Episerver has an attribute named ImageUrlAttribute . Create a derived type that has a default constructor that sets the path to a default image file:
public class StartPageImageUrlAttribute : ImageUrlAttribute
{
public StartPageImageUrlAttribute() : base("/static/contenticons/StartPage.png")
{ }
public StartPageImageUrlAttribute(string path) : base(path)
{ }
}
Apply this attribute to your content type classes to show a default icon: