Episerver create page programmatically - episerver

I am using this code
var parent = ContentReference.StartPage;
IContentRepository contentRepository = EPiServer.ServiceLocation.ServiceLocator.Current.GetInstance<IContentRepository>();
PageData myPage = contentRepository.GetDefault<LoginPage>(parent);
myPage.PageName = "My new page";
var page = contentRepository.GetChildren<LoginPage>(parent).FirstOrDefault(name => name.Name == myPage.Name);
if (page == null)
contentRepository.Save(myPage, EPiServer.DataAccess.SaveAction.Publish);
to create a page programatically. The thing is I am not sure where to put this code?
I don't want to show LoginPage which is page type to show in the list in the admin/edit panel as I want to create only one page under that page type. Maybe there is another way where I can just create a stand alone page and don't have to create the page type or maybe use an already made page type.
This is the code for my page type
[ContentType(DisplayName = "Custom Login Page", GUID = "c0d358c3-4789-4e53-bef3-6ce20efecaeb", Description = "")]
public class LoginPage : StandardPage
{
/*
[CultureSpecific]
[Display(
Name = "Main body",
Description = "The main body will be shown in the main content area of the page, using the XHTML-editor you can insert for example text, images and tables.",
GroupName = SystemTabNames.Content,
Order = 1)]
public virtual XhtmlString MainBody { get; set; }
*/
}
Then I am creating a model like this
public class LoginModel : PageViewModel<LoginPage>
{
public LoginFormPostbackData LoginPostbackData { get; set; } = new LoginFormPostbackData();
public LoginModel(LoginPage currentPage)
: base(currentPage)
{
}
public string Message { get; set; }
}
public class LoginFormPostbackData
{
public string Username { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
}
And my controller looks like this
public ActionResult Index(LoginPage currentPage, [FromUri]string ReturnUrl)
{
var model = new LoginModel(currentPage);
model.LoginPostbackData.ReturnUrl = ReturnUrl;
return View(model);
}
Do you think there is another way to do it? I will also show my login view
#using EPiServer.Globalization
#model LoginModel
<h1 #Html.EditAttributes(x =>
x.CurrentPage.PageName)>#Model.CurrentPage.PageName</h1>
<p class="introduction" #Html.EditAttributes(x =>
x.CurrentPage.MetaDescription)>#Model.CurrentPage.MetaDescription</p>
<div class="row">
<div class="span8 clearfix" #Html.EditAttributes(x =>
x.CurrentPage.MainBody)>
#Html.DisplayFor(m => m.CurrentPage.MainBody)
</div>
#if (!User.Identity.IsAuthenticated &&
!User.IsInRole("rystadEnergyCustomer"))
{
<div class="row">
#using (Html.BeginForm("Post", null, new { language = ContentLanguage.PreferredCulture.Name }))
{
<div class="logo"></div>
#Html.AntiForgeryToken()
<h2 class="form-signin-heading">Log in</h2>
#Html.LabelFor(m => m.LoginPostbackData.Username, new { #class = "sr-only" })
#Html.TextBoxFor(m => m.LoginPostbackData.Username, new { #class = "form-control", autofocus = "autofocus" })
#Html.LabelFor(m => m.LoginPostbackData.Password, new { #class = "sr-only" })
#Html.PasswordFor(m => m.LoginPostbackData.Password, new { #class = "form-control" })
<div class="checkbox">
<label>
#Html.CheckBoxFor(m => m.LoginPostbackData.RememberMe)
#Html.DisplayNameFor(m => m.LoginPostbackData.RememberMe)
</label>
</div>
#Html.HiddenFor(m => m.LoginPostbackData.ReturnUrl, "/login-customers")
<input type="submit" value="Log in" class="btn btn-lg btn-primary btn-block" />
}
#Html.DisplayFor(m => m.Message)
</div>
}
else
{
<span>Welcome #User.Identity.Name</span>
#Html.ActionLink("Logout", "Logout", "LoginPage", null, null);
}

I think you're misunderstanding some of the Episerver concepts.
If you don't want it to be a page in Episerver, you shouldn't use PageController, page types, or templates. Instead, just use a standard controller and view to create your login page.
Otherwise, you do have to create a page of type LoginPage, which will be visible in the page tree. No need to create it programmatically, you can just add the page manually and then hide the LoginPage type from edit mode to avoid editors creating additional login pages.

Related

How to list checked enum values only?

I’m learning Blazor and was trying to put/save in a list some enum elements, only the ones that are checked. I have read loads of hints on stackoverflow and other web sites but am still unable to achieve that, I know something is missing but I’m blind for now
Let’s say I have an enum in a separate class calle Enums:
public enum Browsers
{
Chrome,
Edge,
Firefox,
Opera,
Safari,
Vivaldi
}
Here is the html part:
#page "/Sub2"
#using TestPatternBuilder.Data
<div class="col">
<div>Browsers:</div>
#foreach (var browser in Enum.GetValues<Browsers>())
{
<input class="form-check-input mx-0" type="checkbox" id="browsers" value="#browser" />
<label class="ms-1" for="browsers">#browser</label><br />
}
<button class="btn btn-secondary my-3" #onclick="AddBrowsers">Add Browsers</button>
<ul class="mt-2">
#foreach (var br in selectedBrowsers)
{
<li>#br.BrowserName</li>
}
</ul>
</div>
And the code part:
#code{
List<TestBrowser> selectedBrowsers = new List<TestBrowser>();
private void AddBrowsers()
{
foreach (Browsers item in Enum.GetValues(typeof(Browsers)))
{
selectedBrowsers.Add(new TestBrowser { BrowserName = item, isChecked = true });
}
}
}
I seem to have it all wrong, tried to bind without success, no idea where the isChecked state is missing...
[enter image description here](https://i.stack.imgur.com/R7y6a.png)
To achive this you'll need some sort of object to hold both your checked state as well as the enum value. For example:
public class SelectableBrowsers
{
public bool IsChecked { get; set; }
public Browsers Browser { get; set; }
}
Then you can generate a List of all enum values like this:
private List<SelectableBrowsers> _browsers = new List<SelectableBrowsers>();
protected override void OnInitialized()
{
foreach (var browser in Enum.GetValues<Browsers>())
{
_browsers.Add(new SelectableBrowsers
{
Browser = browser
});
}
}
Now you can output the browsers based on your generated list like this:
#foreach (var browser in _browsers)
{
<input #bind="browser.IsChecked" class="form-check-input mx-0" type="checkbox" id="browsers" />
<label class="ms-1" for="browsers">#browser.Browser</label><br />
}
Finally in your AddBrowsers you can loop every selected element like this:
private void AddBrowsers()
{
foreach (selectedBrowsers browser in _browsers.Where(x => x.IsChecked))
{
selectedBrowsers.Add(new TestBrowser { BrowserName = item.Browser, isChecked = true });
}
}
Hope this helps :)
An interesting alternative using Enum Flags:
#page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<div>
#foreach (var browser in Enum.GetValues<Browsers>())
{
<div>
<input class="form-check-input mx-0" type="checkbox" id="browsers" value="#(isSelected(browser))" #onchange="() => AddToList(browser)" />
<label class="ms-1" for="browsers">#browser</label>
</div>
}
</div>
<div class="alert alert-info mt-2">
#foreach (var browser in Enum.GetValues<Browsers>())
{
#if (browser == (selectedBrowsers & browser))
{
<div>
#browser
</div>
}
}
</div>
#code {
private Browsers selectedBrowsers;
private bool isSelected(Browsers browser)
=> (selectedBrowsers & browser) == browser;
private Task AddToList(Browsers browser)
{
if ((selectedBrowsers & browser) == browser)
selectedBrowsers &= ~browser;
else
selectedBrowsers = selectedBrowsers | browser;
return Task.CompletedTask;
}
public enum Browsers
{
Chrome = 1,
Edge = 2,
Firefox = 4,
Opera = 8,
Safari = 16,
Vivaldi = 32
}
}

Setting a value in Controller from Visualforce Page is taking too long

I'm using Visualforce Page to display some selection fields, and based on the selections I'm updating my list.
<apex:form>
<div class="map-controls">
<div class="map-selects">
<apex:selectList value="{!state}" multiselect="false" size="1">
<apex:selectOptions value="{!states}"></apex:selectOptions>
<apex:actionSupport event="onchange" rerender="countyList" />
</apex:selectList>
<apex:selectList value="{!county}" multiselect="false" size="1" id="countyList">
<apex:selectOptions value="{!counties}"></apex:selectOptions>
</apex:selectList>
</div>
<div class="map-search">
<apex:commandButton value="Search" action="{!test}" rerender="productlistpanel" status="status" />
</div>
<div class="radio-btns">
<apex:selectRadio value="{!type}">
<apex:selectOptions value="{!types}" />
</apex:selectRadio>
</div>
</div>
</apex:form>
Basically what I'm trying to do here is, when user selects the State, County and Type upon clicking the commandButton, the product list will be rendered.
<apex:outputPanel id="productlistpanel">
<div class="splide" role="group">
<div class="splide__track">
<ul class="splide__list">
<apex:repeat value="{!products}" var="productKey" id="theRepeat">
<!-- REPEAT CONTENT -->
</apex:repeat>
</ul>
</div>
</div>
<script>
document.dispatchEvent(new CustomEvent("splideTest", { "detail": 'TEST' }));
</script>
</apex:outputPanel>
And this is my controller.
public List<SelectOption> getTypes() {
RecordTypeInfo TYPE1 = Schema.SObjectType.Product2.getRecordTypeInfosByDeveloperName().get('TYPE1');
RecordTypeInfo TYPE2 = Schema.SObjectType.Product2.getRecordTypeInfosByDeveloperName().get('TYPE2');
List<SelectOption> options = new List<SelectOption>();
options.add(new SelectOption(TYPE1.getRecordTypeId(), 'TYPE1'));
options.add(new SelectOption(TYPE2.getRecordTypeId(), 'TYPE2'));
return options;
}
public List<SelectOption> getStates() {
List<SelectOption> options = new List<SelectOption>();
options.add(new SelectOption('All', 'All'));
List<State__c> states = [SELECT Id,
Name
FROM State__c];
for (State__c s : states) {
options.add(new SelectOption(s.Name, s.Name));
}
return options;
}
public List<SelectOption> getCounties() {
List<SelectOption> options = new List<SelectOption>();
options.add(new SelectOption('All', 'All'));
List<County__c> counties = new List<County__c>();
if (state != null && state != 'ALL') {
counties = [SELECT Id,
State__c,
Name
FROM County__c
WHERE State__r.Name = :state];
}
for (County__c c : counties) {
options.add(new SelectOption(c.Name, c.Name));
}
return options;
}
public PageReference test() {
return null;
}
public String state { get; set; }
public String county { get; set; }
public String type { get; set; }
public Map<Id, WRAPPER> productList { get; set; }
public Map<Id, WRAPPER> getProducts() {
try {
// CREATE QUERY
query += String.isNotBlank(state) && state != 'ALL' ? ' AND State__c = \'' + state + '\'' : '';
query += String.isNotBlank(county) && county != 'ALL' ? ' AND County__c = \'' + county + '\'' : '';
query += String.isNotBlank(type) ? ' AND RecordTypeId = \'' + type + '\'' : '';
query += ' ORDER BY Name ASC';
System.debug('query ' + query);
List<Product2> productList = (List<Product2>)database.query(query);
for (Product2 prod : productList) {
// CREATE LIST
}
return returnMap;
} catch (Exception ex) {
ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.Error, ex.getMessage());
ApexPages.addMessage(msg);
return null;
}
}
My problem is when I select the type and hit Search; the type is not available right away. I can see the rerender is being executed and at the logs I see that type is not saved. I have to click Search button again to see the Type selected as I wanted it to be.
I usually use Lightning Web Component and I don't deal with this kind of problem but unfortunately, I have to use VF Page for this implementation.
I could not locate any work-around so far, I tried to understand the delay, but it seems like it is not a common issue. I assume it is a design issue on my end. I couldn't locate why setting the value is not fast enough.
Does that happen to you, or do you have any suggestions for it?
You could cheat, write it in LWC and then use "Lightning Out" to display it. Saves the hassle of eventually rewriting it ;)
I suspect part of it is that only 1st picklist has apex:actionSupport.
You swallow the exception (maybe there's an issue with the query) and use addMessage - but for it to truly show you need <apex:pageMessages id="messages" /> tag and then add it to your rerender (rerender="productlistpanel,messages")
Your "type" picklist radio is the only one without fallback "ALL". I suspect in UI it apppears to have type1 selected as 1st entry but really the value is null because you didn't initalise it in controller? See if it behaves better if you explicitly set it in constructor for example.
Risko of soql injection (could use bind variables, even in dynamic soql), you could read about <apex:actionRegion> for such partial form submits. Shameless plug: https://salesforce.stackexchange.com/a/22216/799

Display Identity seed when creating a new sql object from MVC application

In my MVC application I allow users to add an object to a database table through a "Create" controller/view using Entity Framework database first design. When testing I have no data in this table currently so the first object should have an ID of 1.
But in the view, the field for ID is empty. Again, it should be 1 since this is the first object and it's picking up from the data model. The next time they add another object, the ID should be 2...etc.
I have set IsIdentity to Yes. I have set Identity seed to 1.
Programatically what am I missing for the ID field to display 1 when this is the first item to be added to the table.
View
<div class="form-group">
#Html.LabelFor(model => model.ProductId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.ProductId, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.ProductId, "", new { #class = "text-danger" })
</div>
</div>
Controller
// GET: Products/Create
public ActionResult Create()
{
return View();
}
// POST: Products/Create
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ProductId,...Other columns")] Product product)
{
if (ModelState.IsValid)
{
db.Products.Add(product);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(product);
}
Model
public int ProductId { get; set; }
SQL
[dbo].[Product]
(
[ProductId] [INT] IDENTITY(1,1) NOT NULL
)
https://i.stack.imgur.com/fGa3i.png

How to insert an image into sql server database?

As said, i'm trying to insert an image in a table, where the type of the field is Varbinary.
What i've done so far :
I've a form with many fields:
#using (Html.BeginForm("InsertProduct", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>PRODUCT</legend>
<div class="editor-label">
#Html.LabelFor(model => model.PRODUCT_ID)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PRODUCT_ID)
#Html.ValidationMessageFor(model => model.PRODUCT_ID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PRODUCT_NAME)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.PRODUCT_NAME)
#Html.ValidationMessageFor(model => model.PRODUCT_NAME)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.PRODUCT_IMAGE)
</div>
<div class="editor-field">
<input type="file" name="PRODUCT_IMAGE" id="PRODUCT_IMAGE" style="width: 100%;" />
</div>
<p>
<input type="submit" value="Create" class="btn btn-primary"/>
</p>
</fieldset>
}
And all these fields allow me to construct a PRODUCT object in my controller :
public ActionResult InsertProduct(PRODUCT ord)
{
MigrationEntities1 sent = new MigrationEntities1();
sent.PRODUCT.Add(ord);
sent.SaveChanges();
List<PRODUCT> Products = sent.PRODUCT.ToList();
return View("Products", Products);
}
But when i'm trying to upload the image (by clicking on Create button), i've the following :
entry is not a valid base64 string because it contains a character
that is not base 64
So first of all : is it the right way to deal with images and second, I think I need to do a pre-treatemant on my image to insert it : how to o that ?
Thanks !
Edit :
Thanks to answers received, seems to be good for insertion. But for displaying, I still have issues (only the "not found image" piture is displayed). I've try to do it two ways :
1.
<img src="LoadImage?id=#Model.product.PRODUCT_ID"/>
and in the controller
public Image LoadImage(int id)
{
String serviceAddress = ConfigurationManager.AppSettings["WCFADDRESS"];
DataServiceContext context = new DataServiceContext(new Uri(serviceAddress));
PRODUCT product = context.Execute<PRODUCT>(new Uri(serviceAddress + "prod_id?prod_id=" + id)).ToList().FirstOrDefault();
MemoryStream ms = new MemoryStream(product.PRODUCT_IMAGE);
Image img = Image.FromStream(ms);
return img;
}
And 2. :
#{
if (Model.product.PRODUCT_IMAGE != null)
{
WebImage wi = new WebImage(Model.product.PRODUCT_IMAGE);
wi.Resize(700, 700,true, true);
wi.Write();
}
}
But none of them are working. What am I doing wrong ?
1) Change your database table to have these columns:
1: ProductImage - varbinary(MAX)
2: ImageMimeType - varchar(50)
2) Change your action method like this:
public ActionResult InsertProduct(PRODUCT ord,
HttpPostedFileBase PRODUCT_IMAGE)
{
if (ModelState.IsValid)
{
MigrationEntities1 sent = new MigrationEntities1();
if (image != null)
{
ord.ProductImage= new byte[PRODUCT_IMAGE.ContentLength];
ord.ImageMimeType = PRODUCT_IMAGE.ContentType;
PRODUCT_IMAGE.InputStream.Read(ord.ProductImage, 0,
PRODUCT_IMAGE.ContentLength);
}
else
{
// Set the default image:
Image img = Image.FromFile(
Server.MapPath(Url.Content("~/Images/Icons/nopic.png")));
MemoryStream ms = new MemoryStream();
img.Save(ms, ImageFormat.Png); // change to other format
ms.Seek(0, SeekOrigin.Begin);
ord.ProductImage= new byte[ms.Length];
ord.ImageMimeType= "image/png";
ms.Read(ord.Pic, 0, (int)ms.Length);
}
try
{
sent.PRODUCT.Add(ord);
sent.SaveChanges();
ViewBag.HasError = "0";
ViewBag.DialogTitle = "Insert successful";
ViewBag.DialogText = "...";
}
catch
{
ViewBag.HasError = "1";
ViewBag.DialogTitle = "Server Error!";
ViewBag.DialogText = "...";
}
List<PRODUCT> Products = sent.PRODUCT.ToList();
return View("Products", Products);
}
return View(ord);
}
This action method is just for create. you need some works for edit and index too. If you have problem to doing them, tell me to add codes of them to the answer.
Update: How to show images:
One way to show stored images is as the following:
1) Add this action method to your controller:
[AllowAnonymous]
public FileContentResult GetProductPic(int id)
{
PRODUCT p = db.PRODUCTS.FirstOrDefault(n => n.ID == id);
if (p != null)
{
return File(p.ProductImage, p.ImageMimeType);
}
else
{
return null;
}
}
2) Add a <img> tag in the #foreach(...) structure of your view (or wherever you want) like this:
<img width="100" height="100" src="#Url.Action("GetProductPic", "Products", routeValues: new { id = item.ID })" />
Change the Image type on the sql sever to Byte[] and use something like this. This is how I have stored images in the past.
http://www.codeproject.com/Articles/15460/C-Image-to-Byte-Array-and-Byte-Array-to-Image-Conv
If not, you can always just store the image locally and pass the image location through a string into the SQL data base, this method works well and is quick to set up.
So, here are the modifications to do :
To insert data in the database :
[HttpPost]
public ActionResult InsertProduct(PRODUCT ord, HttpPostedFileBase image)
{
MigrationEntities1 sent = new MigrationEntities1();
if (image != null)
{
ord.PRODUCT_IMAGE = new byte[image.ContentLength];
image.InputStream.Read(ord.PRODUCT_IMAGE, 0, image.ContentLength);
}
sent.PRODUCT.Add(ord);
sent.SaveChanges();
List Products = sent.PRODUCT.ToList();
return View("Products", Products);
}
Note: this is the "light" way, for something that is more complete, have a look to Amin answer.
For displaying :
In the view
<img src="LoadImage?id=#Model.product.PRODUCT_ID"/>
And in the controller :
public FileContentResult LoadImage(int id)
{
String serviceAddress = ConfigurationManager.AppSettings["WCFADDRESS"];
DataServiceContext context = new DataServiceContext(new Uri(serviceAddress));
PRODUCT product = context.Execute<PRODUCT>(new Uri(serviceAddress + "prod_id?prod_id=" + id)).ToList().FirstOrDefault();
return new FileContentResult(product.PRODUCT_IMAGE, "image/jpeg");
}
And everything is ok now, thanks !

Having some problems saving/displaying an image in MVC 4

I have searched around for a while now with no joy. I am trying to save an image to my SQL db as a byte array, then I am trying to display it later. The display part is not working. I don't know if it's a problem with the save or the display. The save appears to be working ok, I can see 'Binary Data' in my SQL table. Any suggestions?
What's happening is that I get a broken image icon on my page. Even if I manually goto the URL e.g. .../Treatments/LoadImage/14 it's broken.
Model contains this in my table definition:
public byte[] Photo { get; set; }
Create View:
<div class="editor-label">
#Html.LabelFor(model => model.Photo)
</div>
<div class="editor-field">
<input type="file" name="photo" />
</div>
Create Controller:
[HttpPost]
public ActionResult Create([Bind(Exclude = "Photo")]Treatment treatment)
{
if (ModelState.IsValid)
{
treatment.Photo = GetByteArrayFromFile();
treatment.WebOrder = db.Treatments.Count();
db.Treatments.Add(treatment);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TreatmentTypeId = new SelectList(db.TreatmentTypes, "Id", "Name", treatment.TreatmentTypeId);
return View(treatment);
}
private byte[] GetByteArrayFromFile() {
int fileLength = Request.Files["photo"].ContentLength;
byte[] byteArray = new byte[fileLength];
return byteArray;
}
Display View:
<div class="display-label">
#Html.DisplayNameFor(model => model.Photo)
</div>
<div class="display-field">
<img src="#Url.Action("LoadImage", new { Id = Model.Id })" />
</div>
LoadImage Controller:
public ActionResult LoadImage(int Id) {
byte[] bytes = db.Treatments.Find(Id).Photo;
return File(bytes, "image/jpeg");
}
I have added:
#using (Html.BeginForm("Create", "Treatments", FormMethod.Post, new { enctype = "multipart/form-data" })) {
// View Code Omitted
}
to my Create view.
Is there something elementary wrong with my code? Any suggestions? Thanks.
It was a problem with my GetByteArrayFromFile Method. My original method above returned an array of zeros only. Using this, I fixed my issue:
private byte[] GetByteArrayFromFile() {
WebImage image = WebImage.GetImageFromRequest();
byte[] byteArray = image.GetBytes();
return byteArray;
}

Resources