Object Reference Error with BeginRenderLink in Sitecore MVC - sitecore

The following code is throwing an "Object reference not set to an instance of an object." error and I can't figure out why. When I debug, my Model has the data I'm expecting. It seems very simple, not sure what I'm doing wrong.
#using(BeginRenderLink(Model, x => x.Page, isEditable: true))
{
#RenderImage(Model, x => x.IconImage, isEditable: true);
#Editable(Model, x => x.Headline);
}
UPDATE: If I take the following two lines out of the BeginRenderLink block, they work as expected. Hopefully that makes sense to one of you...
#RenderImage(Model, x => x.IconImage, isEditable: true);
#Editable(Model, x => x.Headline);
Stack Trace
[NullReferenceException: Object reference not set to an instance of an object.]
Glass.Mapper.Sc.Web.Mvc.GlassView`1.BeginRenderLink(T model, Expression`1 field, NameValueCollection attributes, Boolean isEditable) +36
ASP._Page_Views_Websites_Pool_Components_IconLink_IconLink_cshtml.Execute() in c:\inetpub\wwwroot\PentairPoolTest\Website\Views\Websites\Pool\Components\IconLink\IconLink.cshtml:5
System.Web.WebPages.WebPageBase.ExecutePageHierarchy() +252
System.Web.Mvc.WebViewPage.ExecutePageHierarchy() +146
System.Web.WebPages.WebPageBase.ExecutePageHierarchy(WebPageContext pageContext, TextWriter writer, WebPageRenderingBase startPage) +106
System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context) +374
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList`1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +89
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList`1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +833
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilterRecursive(IList`1 filters, Int32 filterIndex, ResultExecutingContext preContext, ControllerContext controllerContext, ActionResult actionResult) +833
System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) +81
System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +710
Data model for relevant fields:
/// <summary>
/// The Page field.
/// <para></para>
/// <para>Field Type: General Link</para>
/// <para>Field ID: 50770754-ec0c-4ac3-a05c-44aa8a278f69</para>
/// <para>Custom Data: </para>
/// </summary>
[GeneratedCodeAttribute("Team Development for Sitecore - GlassItem.tt", "1.1")]
[SitecoreField("Page")]
public virtual Link Page {get; set;}
/// <summary>
/// The IconImage field.
/// <para></para>
/// <para>Field Type: Image</para>
/// <para>Field ID: c5b9417e-2b29-461d-9e95-396fac25b3bb</para>
/// <para>Custom Data: </para>
/// </summary>
[GeneratedCodeAttribute("Team Development for Sitecore - GlassItem.tt", "1.1")]
[SitecoreField("IconImage")]
public virtual Image IconImage {get; set;}
/// <summary>
/// The Headline field.
/// <para></para>
/// <para>Field Type: Single-Line Text</para>
/// <para>Field ID: 16f2ca59-68b4-412d-a021-7fdceaa50cb1</para>
/// <para>Custom Data: </para>
/// </summary>
[GeneratedCodeAttribute("Team Development for Sitecore - GlassItem.tt", "1.1")]
[SitecoreField("Headline")]
public virtual string Headline {get; set;}

Related

Binding a List in a ObservableCollection in XAML - Specified cast not valid exception

I have an ObservableCollection<List<Model>> Data in my ViewModel.
In my Page I need a CarouselView, in which each ItemTemplate shows the data of the Data list in a ListView.
Currently, I am doing that in that way:
<CarouselView ItemsSource="{Binding Data}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
...
<ListView ItemsSource="{Binding .}">
...
</ListView>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
In the way I am doing that I get a "Specified cast not valid" exception, in which I see the following additional information:
{System.InvalidCastException: Specified cast is not valid.
at (wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].ActivateContent (System.Int32 index, System.Object item) [0x00032]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].CreateContent (System.Int32 index, System.Object item, System.Boolean insert) [0x00000]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].GetOrCreateContent (System.Int32 index, System.Object item) [0x00023]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Internals.TemplatedItemsList`2[TView,TItem].get_Item (System.Int32 index) [0x0000e]
in <62e3629c74b84e3d834046331d2bb5f8>:0
at Xamarin.Forms.Platform.iOS.ListViewRenderer+ListViewDataSource.GetCellForPath (Foundation.NSIndexPath indexPath) [0x00007]
in D:\a\_work\1\s\Xamarin.Forms.Platform.iOS\Renderers\ListViewRenderer.cs:1397
at Xamarin.Forms.Platform.iOS.ListViewRenderer+ListViewDataSource.GetCell (UIKit.UITableView tableView, Foundation.NSIndexPath indexPath) [0x00021]
in D:\a\_work\1\s\Xamarin.Forms.Platform.iOS\Renderers\ListViewRenderer.cs:1105
at (wrapper managed-to-native) UIKit.UIApplication.UIApplicationMain(int,string[],intptr,intptr)
at UIKit.UIApplication.Main (System.String[] args, System.Type principalClass, System.Type delegateClass) [0x0003b]
in /Users/builder/azdo/_work/1/s/xamarin-macios/src/UIKit/UIApplication.cs:85
at App.iOS.Application.Main (System.String[] args) [0x00001]
in <Path>\Main.cs:18 }
The Model holds only string values, so the exception cannot come from this.
I'm not sure why you're getting that specific exception. I couldn't get the ListView inside of a CarouselView to work either.
However, it works when you use a bindable StackLayout instead of a ListView. My guess is that the bindable StackLayout doesn't support scrolling and thus doesn't fight with the CarouselView but I don't know.
MainPage, MainViewModel and items
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Xamarin.Forms;
namespace App1
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}
public class MainViewModel
{
public ObservableCollection<Item> Data { get; }
public MainViewModel()
{
Data = new ObservableCollection<Item>(GenerateItems());
}
private IEnumerable<Item> GenerateItems()
{
return Enumerable.Range(1, 10)
.Select(a => new Item
{
ItemTitle = $"Item {a}",
SubItems = Enumerable.Range(1, 10).Select(b => new SubItem { SubItemTitle = $"SubItem {b}" }).ToList()
});
}
}
public class Item
{
public string ItemTitle { get; set; }
public List<SubItem> SubItems { get; set; }
}
public class SubItem
{
public string SubItemTitle { get; set; }
}
}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="App1.MainPage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:App1">
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<CarouselView ItemsSource="{Binding Data}">
<CarouselView.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding ItemTitle}" />
<StackLayout BindableLayout.ItemsSource="{Binding SubItems}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding SubItemTitle}" />
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
</StackLayout>
</DataTemplate>
</CarouselView.ItemTemplate>
</CarouselView>
</ContentPage>
Result:

Create tags on restored AWS EC2 Instance from AWS Backup

I have a number of EC2 instances in AWS that are backed up using the AWS Backup service. These instances all have a number of tags that are utilised for different purposes, from what backup schedule to apply, to automated startup/shutdown routines.
From the testing of the restore process and a couple of days trawling the internet it seems that these tags are not restored along with the instance.
In a DR scenario where a AZ is down and we need to restore these instances to a new AZ we would need the approprate tags in each of the restored instances.
Am I missing something with the backup/restore process?
If not, then I was looking into an automated process using a lambda function called from a cloudwatch event but I can't see an appropriate event for an instance restore (perhaps because it creates a new instance based on the backup?).
I'd need the tags and their values from the instance that was backed up created on the new restored instance, if there is an event for the restore I'd assume it would have the instanceid for both of these so I could get and set the tags, but if I can only use a create instance event I don't know how I can get the original tag values.
Anyone got a solution for this?
I managed to get a workable solution, with a C# lambda function triggered off a CloudWatch event when an instance is restored.
I've posted the solution below in case it helps someone else out, at least until they add tags as part of the restore process.
I followed this guide for creating the lambda function, but the main steps are below. In a cmd prompt:
dotnet new -i Amazon.Lambda.Templates
dotnet new lambda.EmptyFunction --name MySimpleFunction --profile default --region us-east-1
dotnet tool install -g Amazon.Lambda.Tools
Then open the solution and add a new class called AWS and add the below code. This does most of the work getting the tags from the old instance and adding to the new instance.
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Amazon.EC2;
using Amazon.EC2.Model;
using Amazon.CloudTrail;
using Amazon.CloudTrail.Model;
using System.Threading.Tasks;
namespace RestoreEc2Tags
{
/// <summary>
/// Class for interacting with Amazon AWS services through the .Net SDK
/// </summary>
public class AWS
{
/// <summary>
/// Gets a list of all Ec2 Instances that do not have a name tag set, assumption being that these
/// will be the restored instances (missing all of the tags).
/// </summary>
/// <returns>List<tTag/></returns>
public async Task<List<Ec2Instance>> GetNewInstancesAsync()
{
AmazonEC2Client client = new AmazonEC2Client();
DescribeInstancesRequest ec2Request = new DescribeInstancesRequest();
DescribeInstancesResponse ec2Response = await client.DescribeInstancesAsync(ec2Request);
var instances = new List<Instance>();
foreach (var reservation in ec2Response.Reservations)
{
for (int i = 0; i < reservation.Instances.Count; i++)
{
instances.Add(reservation.Instances[i]);
}
}
var ec2Instances = new List<Ec2Instance>();
foreach (var instance in instances)
{
var ec2Instance = new Ec2Instance();
var tags = instance.Tags;
var nameTag = tags.Where(t => t.Key == "Name").FirstOrDefault();
bool isRestored = nameTag == null || nameTag.Value == null ? true : false;
ec2Instance.InstanceId = instance.InstanceId;
ec2Instance.State = instance.State.Name;
if (isRestored)
{
ec2Instances.Add(ec2Instance);
}
}
return ec2Instances;
}
/// <summary>
/// Gets all of the tags from the EC2 instance that the backup was from.
/// </summary>
/// <param name="originalInstanceId"></param>
/// <param name="startTime"></param>
/// <param name="endTime"></param>
/// <returns>List<tTag/></returns>
public async Task<List<Amazon.EC2.Model.Tag>> GetTagsAsync(string originalInstanceId, DateTime startTime, DateTime endTime)
{
var tagsToAdd = new List<Amazon.EC2.Model.Tag>();
var region = Amazon.RegionEndpoint.APSoutheast2;
var client = new AmazonCloudTrailClient(new AmazonCloudTrailConfig
{
Timeout = TimeSpan.FromSeconds(300), // Default value is 100 seconds
RegionEndpoint = region
});
LookupEventsRequest cloudTrailRequest = new LookupEventsRequest();
LookupAttribute eventName = new LookupAttribute { AttributeKey = "eventName", AttributeValue = "CreateTags" };
var lookupAttributes = new List<LookupAttribute>();
lookupAttributes.Add(eventName);
cloudTrailRequest.LookupAttributes = lookupAttributes;
cloudTrailRequest.StartTime = startTime;
cloudTrailRequest.EndTime = endTime;
var lookupEvents = await client.LookupEventsAsync(cloudTrailRequest);
var awsBackupCloudTrailEvent = lookupEvents.Events.Where(t => t.Username == "AWSBackup-AWSBackupDefaultServiceRole")
.Where(t => t.Resources
.Any(y => y.ResourceName.StartsWith("ami")))
.Where(t => t.CloudTrailEvent.Contains(originalInstanceId)).FirstOrDefault().CloudTrailEvent;
JObject o = JObject.Parse(awsBackupCloudTrailEvent);
JObject requestParameters = JObject.Parse(o["requestParameters"].ToString());
JObject tagSet = JObject.Parse(requestParameters["tagSet"].ToString());
var tagsFromCloudTrail = JsonConvert.DeserializeObject<JsonArray>(tagSet.ToString());
foreach (var item in tagsFromCloudTrail.Items)
{
var key = item["key"];
var value = "";
if (item.Count > 1)
{
value = item["value"];
}
var newTag = new Amazon.EC2.Model.Tag
{
Key = key,
Value = value
};
//aws: are reserved tags
if (!key.Contains("aws:"))
{
tagsToAdd.Add(newTag);
}
}
return tagsToAdd;
}
/// <summary>
/// Gets the InstanceId and Date of the newly restored EC2 instance from an AmiId. This Instance will need the tags restored to it.
/// </summary>
/// <param name="ami"></param>
/// <returns></returns>
public async Task<BackupDetails> GetBackupDetailsAsync(string ami)
{
var backupDetails = new BackupDetails();
var region = Amazon.RegionEndpoint.APSoutheast2;
var client = new AmazonCloudTrailClient(new AmazonCloudTrailConfig
{
Timeout = TimeSpan.FromSeconds(300), // Default value is 100 seconds
RegionEndpoint = region
});
LookupEventsRequest cloudTrailRequest = new LookupEventsRequest();
LookupAttribute eventName = new LookupAttribute { AttributeKey = "eventName", AttributeValue = "RestoreCompleted" };
var lookupAttributes = new List<LookupAttribute>();
lookupAttributes.Add(eventName);
cloudTrailRequest.LookupAttributes = lookupAttributes;
var lookupEvents = await client.LookupEventsAsync(cloudTrailRequest);
var awsBackupCloudTrailEvents = lookupEvents.Events.Where(t => t.CloudTrailEvent.Contains(ami)).FirstOrDefault();
if (awsBackupCloudTrailEvents != null)
{
var awsBackupCloudTrailEvent = awsBackupCloudTrailEvents.CloudTrailEvent;
JObject o = JObject.Parse(awsBackupCloudTrailEvent);
JObject serviceEventDetails = JObject.Parse(o["serviceEventDetails"].ToString());
var backupServiceEvent = JsonConvert.DeserializeObject<BackupServiceEvent>(serviceEventDetails.ToString());
var resourceArn = backupServiceEvent.resourceArn;
var backupCreationDateEpoch = backupServiceEvent.creationDate.seconds;
var backupCreationDate = UnixTimestampToDateTime(backupCreationDateEpoch);
var findString = "instance/";
int startIndex = resourceArn.IndexOf(findString, 0) + findString.Length;
int length = resourceArn.Length - startIndex;
backupDetails.InstanceId = resourceArn.Substring(startIndex, length);
backupDetails.CreationDate = awsBackupCloudTrailEvents.EventTime;
}
return backupDetails;
}
/// <summary>
/// Returns a DateTime type from a unix epoch timestamp.
/// </summary>
/// <param name="unixTime"></param>
/// <returns>DateTime</returns>
public static DateTime UnixTimestampToDateTime(double unixTime)
{
DateTime unixStart = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
long unixTimeStampInTicks = (long)(unixTime * TimeSpan.TicksPerSecond);
return new DateTime(unixStart.Ticks + unixTimeStampInTicks, System.DateTimeKind.Utc);
}
/// <summary>
/// Gets the AmiId that was used to restore an Instance. Required to find the tags from the original instance that were copied to the backup.
/// </summary>
/// <param name="newInstanceId"></param>
/// <returns>AmiId</returns>
public async Task<string> GetAmiIdFromNewInstanceAsync(string newInstanceId)
{
string amiId = "";
var region = Amazon.RegionEndpoint.APSoutheast2;
var client = new AmazonCloudTrailClient(new AmazonCloudTrailConfig
{
Timeout = TimeSpan.FromSeconds(300),
RegionEndpoint = region
});
LookupEventsRequest cloudTrailRequest = new LookupEventsRequest();
LookupAttribute eventName = new LookupAttribute { AttributeKey = "eventName", AttributeValue = "RunInstances" };
var lookupAttributes = new List<LookupAttribute>();
lookupAttributes.Add(eventName);
cloudTrailRequest.LookupAttributes = lookupAttributes;
var lookupEvents = await client.LookupEventsAsync(cloudTrailRequest);
var awsBackupCloudTrailEvents = lookupEvents.Events.Where(t => t.CloudTrailEvent.Contains(newInstanceId)).FirstOrDefault();
if (awsBackupCloudTrailEvents != null)
{
var awsBackupCloudTrailEvent = awsBackupCloudTrailEvents.CloudTrailEvent;
JObject o = JObject.Parse(awsBackupCloudTrailEvent);
JObject responseElements = JObject.Parse(o["responseElements"].ToString());
JObject instancesSet = JObject.Parse(responseElements["instancesSet"].ToString());
JObject instanceItems = JObject.Parse(instancesSet["items"].First.ToString());
var instance = JsonConvert.DeserializeObject<InstancesSet>(instanceItems.ToString());
amiId = instance.imageId;
}
return amiId;
}
/// <summary>
/// Restores all the tags to an EC2 instance that has been restored.
/// </summary>
/// <param name="instanceId"></param>
/// <param name="tags"></param>
/// <returns></returns>
public async Task<string> RestoreTagsAsync(string instanceId, List<Amazon.EC2.Model.Tag> tags)
{
var response = "";
try
{
AmazonEC2Client client = new AmazonEC2Client();
await client.CreateTagsAsync(new CreateTagsRequest
{
Resources = new List<string> {
instanceId
},
Tags = tags
});
response = "Tags restored successfully.";
}
catch (Exception ex)
{
response = String.Format("There has been an error: {0}:", ex.Message);
}
return response;
}
/// <summary>
/// Ties all the information from the CloudWatch logs together to get the tags associated with a restored instance
/// then calls the function to restore the tags
/// </summary>
/// <param name="newInstanceId"></param>
/// <returns></returns>
public async Task RestoreInstanceTagsAsync(string newInstanceId)
{
var ami = await GetAmiIdFromNewInstanceAsync(newInstanceId);
if (ami != "")
{
var backupDetails = await GetBackupDetailsAsync(ami);
if (backupDetails.InstanceId != null)
{
//Assuming daily backups, so we need to go back and get the last backup which could be up to 24 hours ago
var tags = await GetTagsAsync(backupDetails.InstanceId, backupDetails.CreationDate.AddHours(-24), backupDetails.CreationDate.AddHours(1));
await RestoreTagsAsync(newInstanceId, tags);
}
}
}
}
/// <summary>
/// Class for Deserializing Items collection from AWS json
/// </summary>
public class JsonArray
{
/// <summary>
/// Item collection from a json array retured from AWS
/// </summary>
public IEnumerable<IDictionary<string, string>> Items { get; set; }
}
/// <summary>
/// Class for Deserializing Backup AWS CloudTrail json logs
/// </summary>
public class BackupServiceEvent
{
/// <summary>
/// State of the backup from CloudTrail logs
/// </summary>
public string state { get; set; }
/// <summary>
/// restoreJobId of the backup from CloudTrail logs
/// </summary>
public string restoreJobId { get; set; }
/// <summary>
/// backupVaultName of the backup from CloudTrail logs
/// </summary>
public string backupVaultName { get; set; }
/// <summary>
/// backupVaultArn of the backup from CloudTrail logs
/// </summary>
public string backupVaultArn { get; set; }
/// <summary>
/// recoveryPointArn of the backup from CloudTrail logs
/// </summary>
public string recoveryPointArn { get; set; }
/// <summary>
/// resourceArn of the backup from CloudTrail logs
/// </summary>
public string resourceArn { get; set; }
/// <summary>
/// backupSizeInBytes of the backup from CloudTrail logs. This is used to retrieve the new InstanceId and backup CreationDate.
/// </summary>
public string backupSizeInBytes { get; set; }
/// <summary>
/// iamRoleArn of the backup from CloudTrail logs
/// </summary>
public string iamRoleArn { get; set; }
/// <summary>
/// resourceType of the backup from CloudTrail logs
/// </summary>
public string resourceType { get; set; }
/// <summary>
/// completionDate of the backup from CloudTrail logs
/// </summary>
public AwsDates completionDate { get; set; }
/// <summary>
/// creationDate of the backup from CloudTrail logs
/// </summary>
public AwsDates creationDate { get; set; }
}
/// <summary>
/// Class Deserializing Date structure from AWS CloudTrail json logs
/// </summary>
public class AwsDates
{
/// <summary>
/// Seconds from epoch from AWS dates
/// </summary>
public Int64 seconds { get; set; }
/// <summary>
/// Nano seconds, appended to seconds for precise epoch time
/// </summary>
public Int64 nanos { get; set; }
}
/// <summary>
/// Backup Details Class
/// </summary>
public class BackupDetails
{
/// <summary>
/// InstanceId that was backed up
/// </summary>
public string InstanceId { get; set; }
/// <summary>
/// Creation date of the backup
/// </summary>
public DateTime CreationDate { get; set; }
}
/// <summary>
/// Class for Deserializing Instance details from AWS CloudTrail json logs, or a newly created instance
/// </summary>
public class InstancesSet
{
/// <summary>
/// InstanceId of the newly created instance
/// </summary>
public string instanceId { get; set; }
/// <summary>
/// The AmiId used in the restore of an instance. Required to retrieve the origianal tags
/// </summary>
public string imageId { get; set; }
/// <summary>
/// instanceState of the newly created instance
/// </summary>
public Dictionary<string, string> instanceState { get; set; }
/// <summary>
/// privateDnsName of the newly created instance
/// </summary>
public string privateDnsName { get; set; }
/// <summary>
/// amiLaunchIndex of the newly created instance
/// </summary>
public int amiLaunchIndex { get; set; }
/// <summary>
/// instanceType of the newly created instance
/// </summary>
public string instanceType { get; set; }
/// <summary>
/// launchTime of the newly created instance
/// </summary>
public Int64 launchTime { get; set; }
/// <summary>
/// placement of the newly created instance
/// </summary>
public Dictionary<string, string> placement { get; set; }
/// <summary>
/// platform of the newly created instance
/// </summary>
public string platform { get; set; }
/// <summary>
/// monitoring of the newly created instance
/// </summary>
public Dictionary<string, string> monitoring { get; set; }
/// <summary>
/// subnetId of the newly created instance
/// </summary>
public string subnetId { get; set; }
/// <summary>
/// vpcId of the newly created instance
/// </summary>
public string vpcId { get; set; }
/// <summary>
/// privateIpAddress of the newly created instance
/// </summary>
public string privateIpAddress { get; set; }
/// <summary>
/// stateReason of the newly created instance
/// </summary>
public Dictionary<string, string> stateReason { get; set; }
/// <summary>
/// architecture of the newly created instance
/// </summary>
public string architecture { get; set; }
/// <summary>
/// rootDeviceType of the newly created instance
/// </summary>
public string rootDeviceType { get; set; }
/// <summary>
/// rootDeviceName of the newly created instance
/// </summary>
public string rootDeviceName { get; set; }
/// <summary>
/// blockDeviceMapping of the newly created instance
/// </summary>
public Dictionary<string, string> blockDeviceMapping { get; set; }
/// <summary>
/// virtualizationType of the newly created instance
/// </summary>
public string virtualizationType { get; set; }
/// <summary>
/// hypervisor of the newly created instance
/// </summary>
public string hypervisor { get; set; }
/// <summary>
/// clientToken of the newly created instance
/// </summary>
public string clientToken { get; set; }
}
public class Ec2Instance : IComparable<Ec2Instance>
{
public string Name { get; set; }
public string InstanceId { get; set; }
public string State { get; set; }
public string Environment { get; set; }
public string HostName { get; set; }
public string Type { get; set; }
public string StartStopSchedule { get; set; }
public string DisplaySchedule { get; set; }
public string PartnerInstanceId { get; set; }
public List<string> StartStopPermissions { get; set; }
public int CompareTo(Ec2Instance that)
{
return this.Name.CompareTo(that.Name);
}
}
}
Then replace the function handler in the function class with the below code:
/// <summary>
/// Gets all of the Ec2Instances that do not have a name tag and attempts to restore all of the tags
/// from the instance it was restored from. Triggered by RestoreCompleted CloudWatch log.
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task<string> FunctionHandler(ILambdaContext context)
{
var response = "Error?";
var aws = new AWS();
var newInstances = await aws.GetNewInstancesAsync();
foreach (var newInstance in newInstances)
{
await aws.RestoreInstanceTagsAsync(newInstance.InstanceId);
response = "Success";
}
return response;
}
After that you need to packge the solution as a zip file so you can upload to a lambda function:
dotnet lambda package -c Release -o ../RestoreEc2Tags.zip -f netcoreapp3.1
Then in your AWS console, create a new Lambda function, ensure you pick .net 3.1 framework, and upload the zip file.
Create a trigger for the function on CloudWatch Logs. You may have to set up a log if you do not have one already, and assign appropriate permissions to read/write to the logs for the IAM role used by the Lambda function.
Add a filter for the log trigger as below:
Filter pattern: { $.eventName = "RestoreCompleted" }
Hope this helps someone.

Glass Mapper (4.1.2.67) failing to map rich text field

I've got a few classes defined to map template items as follows:
public class ContentBase
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
[SitecoreParent]
public virtual ContentBase Parent { get; set; }
[SitecoreItem]
public virtual Item Self { get; set; }
}
[SitecoreType(TemplateId = "{7979766D-DB9C-4E75-9BE3-5B481C6AB6FF}", AutoMap = true)]
public class EventsListing : ContentBase
{
[SitecoreField(FieldName = "EventsLocation")]
public virtual SitecoreFolder<Event> Events { get; set; }
}
[SitecoreType(TemplateId = "{CED01C9B-6284-461A-848F-2CDD00CC6DEB}", AutoMap = true)]
public class Event : ContentBase
{
public virtual string Title { get; set; }
public virtual string Details { get; set; }
public virtual string iCalSummary { get; set; }
public virtual Image ImageLandscape { get; set; }
public virtual Image ImagePortrait { get; set; }
public virtual Image ImageSquare { get; set; }
public virtual string Date { get; set; }
public virtual DateTime DateStart { get; set; }
public virtual DateTime DateEnd { get; set; }
public virtual string Location { get; set; }
public virtual string GoogleMapsAddress { get; set; }
public virtual string MemberDiscount { get; set; }
public virtual Link EventLinkUrl { get; set; }
public virtual string EventLinkText { get; set; }
}
The template for the Event
I have a sublayout for the EventsListing based on GlassUserControl and that successfully gets all fields for EventsListing and the children Events. I then have a link on Date to generate an iCal for the Event via a web.api controller
[RoutePrefix("hbf/api/ical")]
public class EventICalController : ApiController
{
[Route("{id:guid}")]
[HttpGet]
public HttpResponseMessage Get(Guid id)
{
var scc = new SitecoreContext();
var item = scc.GetItem<Item>(id);
var myEvent = scc.GetItem<Models.Event>(id);
The item looks fine and I can access the fields; but the call scc.GetItem(id) throws an exception on the "Details" field.
The exception nesting is (full stack trace below):
Glass.Mapper.MapperException "Failed to create type Models.Event"
Glass.Mapper.MapperException "Failed to map properties on /sitecore/content/..."
Glass.Mapper.MapperException "Failed to map property Details on Models.Event"
System.NullReferenceException
If I comment out the "Details" property it works.
I've tried various ways to get the SitecoreContext, even specifying the language to no avail. I've also tried setting the SitecoreField attribute with the name and/or ID and the field type.
What can I in terms of configuration (or something) to resolve this?
{
"Message": "An error has occurred.",
"ExceptionMessage": "Failed to create type xxx.Web.Models.Event",
"ExceptionType": "Glass.Mapper.MapperException",
"StackTrace": " at Glass.Mapper.Pipelines.ObjectConstruction.Tasks.CreateConcrete.CreateConcreteTask.CreateObject(ObjectConstructionArgs args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\ObjectConstruction\\Tasks\\CreateConcrete\\CreateConcreteTask.cs:line 115
at Glass.Mapper.Pipelines.ObjectConstruction.Tasks.CreateConcrete.CreateConcreteTask.Execute(ObjectConstructionArgs args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\ObjectConstruction\\Tasks\\CreateConcrete\\CreateConcreteTask.cs:line 68
at Glass.Mapper.Pipelines.AbstractPipelineRunner`2.<>c__DisplayClass3.<CreateTaskExpression>b__2(T args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\AbstractPipelineRunner.cs:line 77
at Glass.Mapper.Pipelines.AbstractPipelineRunner`2.<>c__DisplayClass3.<CreateTaskExpression>b__2(T args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\AbstractPipelineRunner.cs:line 82
at Glass.Mapper.Pipelines.AbstractPipelineRunner`2.<>c__DisplayClass3.<CreateTaskExpression>b__2(T args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\AbstractPipelineRunner.cs:line 82
at Glass.Mapper.Pipelines.AbstractPipelineRunner`2.<>c__DisplayClass3.<CreateTaskExpression>b__2(T args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\AbstractPipelineRunner.cs:line 82
at Glass.Mapper.Pipelines.AbstractPipelineRunner`2.<>c__DisplayClass3.<CreateTaskExpression>b__2(T args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\AbstractPipelineRunner.cs:line 82
at Glass.Mapper.Pipelines.AbstractPipelineRunner`2.<>c__DisplayClass3.<CreateTaskExpression>b__2(T args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\AbstractPipelineRunner.cs:line 82
at Glass.Mapper.AbstractService.InstantiateObject(AbstractTypeCreationContext abstractTypeCreationContext) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\AbstractService.cs:line 138
at Glass.Mapper.Sc.SitecoreService.CreateType(Type type, Item item, Boolean isLazy, Boolean inferType, Dictionary`2 parameters, Object[] constructorParameters) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper.Sc\\SitecoreService.cs:line 498
at Glass.Mapper.Sc.SitecoreService.GetItem[T](Guid id, Boolean isLazy, Boolean inferType) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper.Sc\\SitecoreService.cs:line 899
at xxx.Web.Services.xxx.EventICalController.Get(Guid id) in d:\\Dev\\LW\\xxx.Web\\Services\\xxx\\EventICalController.cs:line 57
at lambda_method(Closure , Object , Object[] )
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)
at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()",
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Failed to map properties on /sitecore/content/xxx/Home/Living well/Events/datasources/Health and fitness events/xxx Fitness.",
"ExceptionType": "Glass.Mapper.MapperException",
"StackTrace": " at Glass.Mapper.Configuration.AbstractTypeConfiguration.MapPropertiesToObject(Object obj, IAbstractService service, AbstractTypeCreationContext context) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Configuration\\AbstractTypeConfiguration.cs:line 174
at Glass.Mapper.Pipelines.ObjectConstruction.Tasks.CreateConcrete.CreateConcreteTask.CreateObject(ObjectConstructionArgs args) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Pipelines\\ObjectConstruction\\Tasks\\CreateConcrete\\CreateConcreteTask.cs:line 104",
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Failed to map property Details on xxx.Web.Models.Event",
"ExceptionType": "Glass.Mapper.MapperException",
"StackTrace": " at Glass.Mapper.Configuration.AbstractTypeConfiguration.<>c__DisplayClassb.<CreatePropertyExpression>b__a(Object obj, AbstractDataMappingContext context) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Configuration\\AbstractTypeConfiguration.cs:line 123
at Glass.Mapper.Configuration.AbstractTypeConfiguration.MapPropertiesToObject(Object obj, IAbstractService service, AbstractTypeCreationContext context) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Configuration\\AbstractTypeConfiguration.cs:line 144",
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Failed to map to property 'Details' on type 'xxx.Web.Models.Event'",
"ExceptionType": "Glass.Mapper.MapperException",
"StackTrace": " at Glass.Mapper.AbstractDataMapper.MapCmsToProperty(AbstractDataMappingContext mappingContext) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\AbstractDataMapper.cs:line 64
at Glass.Mapper.Configuration.AbstractTypeConfiguration.<>c__DisplayClassb.<CreatePropertyExpression>b__a(Object obj, AbstractDataMappingContext context) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\Configuration\\AbstractTypeConfiguration.cs:line 119",
"InnerException": {
"Message": "An error has occurred.",
"ExceptionMessage": "Object reference not set to an instance of an object.",
"ExceptionType": "System.NullReferenceException",
"StackTrace": " at xxx.Core.Pipelines.RenderField.GetDevModeContent.Process(RenderFieldArgs args)
at (Object , Object[] )
at Sitecore.Pipelines.CorePipeline.Run(PipelineArgs args)
at Glass.Mapper.Sc.DataMappers.SitecoreFieldStringMapper.RunPipeline(Field field) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper.Sc\\DataMappers\\SitecoreFieldStringMapper.cs:line 99
at Glass.Mapper.AbstractDataMapper.MapCmsToProperty(AbstractDataMappingContext mappingContext) in c:\\TeamCity\\buildAgent\\work\\8567e2ba106d3992\\Source\\Glass.Mapper\\AbstractDataMapper.cs:line 60"
}
}
}
}
}
I added the Glass.Mapper.Sc.* projects source to my solution (yay for open source) and stepped through and found that I needed to add some extra configuration for the rich text field, as follows:
[SitecoreField(Setting = SitecoreFieldSettings.RichTextRaw)]
public virtual string Details { get; set; }
This way the field does not go through the render process and just returns the raw HTML, which is what I wanted. It was the render process that failed, this is kind of a half answer as I don't know why the render process failed, but I don't need it. Unfortunately I didn't find what I needed in the Glass.Mapper documentation.
I believe that Glass is failing to resolve the Context.Site. Since web api calls don't have a context site by default. You can probably wrap your entire call inside a using (new SiteContextSwitcher(Factory.GetSite("yoursite"))), or find another way to set the Context.Site inside your service call.
or if you have multiple sites, make sure they have the hostName property defined in your <sites> node, and something like this at the top of the call:
var sites = Sitecore.Configuration.Factory.GetSiteInfoList();
string currentHost = HttpContext.Current.Request.Url.Host;
var currentSite = sites.FirstOrDefault(obj => obj.HostName.Equals(currentHost, StringComparison.InvariantCultureIgnoreCase));
if (currentSite != null)
{
var newSite = new Sitecore.Sites.SiteContext(currentSite);
if (newSite != null)
{
using (new SiteContextSwitcher(newSite))
{
///Code here
}
{
{

#ModelAttribute controller spring-mvc mocking

I want to test a controller which is using #ModelAttribute for one of its method arguments.
public String processSaveAction(#ModelAttribute("exampleEntity") ExampleEntity exampleEntity)
#ModelAttribute method getExampleEntity is using #RequestParam:
#ModelAttribute("exampleEntity")
public ExampleEntity getExampleEntity(#RequestParam(value = "id", required = true) ExampleEntity exampleEntity) {
My controller is using WebDataBinder to call a factory, which returns an object based on param "id".
#Controller
public class ExampleController(){
#Autowired private IdEditorFactory idEditorFactory;
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(ExampleEntity.class, idEditorFactory.createEditor(ExampleEntity.class));
}
#ModelAttribute("exampleEntity")
public ExampleEntity getExampleEntity(#RequestParam(value = "id", required = true) ExampleEntity exampleEntity) {
//Irrelevant operations
return exampleEntity;
}
#RequestMapping(method = RequestMethod.POST, params = "action=save")
public String processSaveAction(
#RequestParam(value = "confirmed") String exampleString,
#ModelAttribute("exampleEntity") ExampleEntity exampleEntity,
BindingResult result, HttpServletRequest request)
throws IOException {
boolean success = editorProcessor.processSaveAction(exampleString,
exampleEntity, result, request);
return success ? getSuccessView(exampleEntity) : VIEW_NAME;
}
}
And my test:
#WebAppConfiguration
public class ExampleControllerTest{
#Mock private EditorProcessor editorProcessor;
#Mock private IdEditorFactory idEditorFactory;
#InjectMocks private ExampleController exampleController;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(exampleController).build();
WebDataBinder webDataBinder = new WebDataBinder(ExampleEntity.class);
webDataBinder.registerCustomEditor(ExampleEntity.class, idEditorFactory.createEditor(ExampleEntity.class));
}
#Test
public void shouldProcessSaveAction() throws Exception {
// given
BindingResult result = mock(BindingResult.class);
ExampleEntity exampleEntity = mock(ExampleEntity.class);
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
given(editorProcessor.processSaveAction("confirmed", exampleEntity, result, httpServletRequest)).willReturn(true);
// when
ResultActions perform = mockMvc.perform(post("/").sessionAttr("exampleEntity", exampleEntity)
.param("id", "123456"
.param("action","save"));
// then
perform.andDo(print())
.andExpect(status().isOk());
}
}
I want to somehow mock getExampleEntity() so that every time I perform a POST with parameter "id", I receive a mocked object ("exampleEntity") for the test.
I could introduce #Binding to the test, but then I would have to mock many levels of methods (like initBinder -> idEditoryFactory-> editor -> hibernateTemplate and so on) only to get an entity from some source (for example, a database).
You can pass in the required #ModelAttribute object with the .flashAttr() method like so:
mockMvc.perform(post("/")
.param("id", "123456")
.param("action","save")
.flashAttr("exampleEntity", new ExampleEntity()));
First, test code shouldn't change our development code. #ModelAttribute will be mount from your param attribute, so .param() is enough. Below is my demo:
#Test
public void registerUser() throws Exception {
System.out.println("hello......." + rob.toString());
RequestBuilder request = post("/register.html")
.param("username", rob.getUsername())
.param("password", rob.getPassword())
.param("firstName", rob.getFirstName())
.param("lastName", rob.getLastName())
.param("email", rob.getEmail())
.with(csrf());
mvc
.perform(request)
.andDo(MockMvcResultHandlers.print())
.andExpect(redirectedUrl("/"));
}
Then is my #Controller:
#Controller
public class LoginController {
#Autowired
private UserService userService;
#RequestMapping(value = "/remove", method = RequestMethod.GET)
public String removeById(#RequestParam("userid") int id, RedirectAttributes attr) {
attr.addFlashAttribute("message", "remove!!!");
attr.addAttribute("mess", "remove ");
return "redirect:/userlist.html";
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String register(#ModelAttribute("user") User user, ModelMap model) {
System.out.println("register " + user.toString());
boolean result = userService.add(user);
model.addAttribute("message", "add " + (result ? "successed" : "failed") + "!!!");
return "/";
}
}
This can submit the right user object to the public String register(#ModelAttribute("user") User user, ModelMap model).
I'm new to Spring MVC, and currently writing a #Controller class but none of the methods have business logic, let alone HTML files for the views under'/static/'. First, I wanted to see how I can Unit Test every method to make sure all end points responded 200/ok before I inserted the business logic, you know Test Driven Development. Then I had difficulty when unit testing #PostMapping annotation method that had a #ModelAttribute assigned to it. After my whole search yesterday, I put together code for someone to unit test such cases involving #PostMapping and #ModelAttribute where you need to update the parameter values of your model attribute on the 'post' method. I'm more than welcome positive feedback to make my tests better, just wanted to post this in cases someone else that's also new wanted to test and make sure that the new info will be saved after the post in the #ModelAttribute without needing a html/jsp file for views for standalone unit testing, look at #Controller2 and String updateQuoteRequest() method for reference, and the last test in class QuoteRequestManagementController_UnitTests for more details.
pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-mvc-hotel-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>serving-web-content</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test Starter is Starter for testing Spring Boot applications
with libraries including JUnit, Hamcrest and Mockito. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Model Attribute Class:
package com.corplithotel.eventsapp.domain;
//Create the Model Attribute class, and its class members
public class QuoteRequest {
String customer;
String age;
String budget;
String eventType;
String foodAllergies;
//getters and setters
public String getCustomer() {
return customer;
}
public void setCustomer(String customer) {
this.customer = customer;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getBudget() {
return budget;
}
public void setBudget(String budget) {
this.budget = budget;
}
public String getEventType() {
return eventType;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
public String getFoodAllergies() {
return foodAllergies;
}
public void setFoodAllergies(String foodAllergies) {
this.foodAllergies = foodAllergies;
}
}
Main Class:
package com.corplithotel.eventsapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class CorpLitHotel {
public static void main(String[] args) {
SpringApplication.run(CorpLitHotel.class, args);
}
}
#Controller1
package com.corplithotel.eventsapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import com.corplithotel.eventsapp.domain.QuoteRequest;
//Step 1: * Create QuoteRequestController *
/*#Conroller annotation makes this class a controller, next we need to
* add 'handler mappings' to provide the controller some functionality.
* For Step 1, we won't add logic for #RequestMapping 'beginQuoteRequest()'
* & #Postrequest 'submitQuoteRequest()' methods, we will Mock the class
* and unit test in Step 2 for TDD examples:
*
*
*/
#Controller
public class QuoteRequestController {
/*#GetMapping annotation is a 'handler mapping' annotation.
* When a user comes to the page to fill out the Quote form, they
* first need to get the page. The return of the method will be a
* 'logical view name', which is just a string, and tends to correlate
* to some HTML, JSP or whatever file you're using for your View.
*
*/
#GetMapping("/newquote")
public String beginQuoteRequest(Model model) {
//Check Unit Test for logic
return "newQuote";
}//beginQuoteRequest()
/*#PosMapping annotation is another 'handler mapping' annotation.
* Once a user fills out the Quote form with their name and
* other event details, they may want to save or post that quote.
* We need to add a handler for the Post, and needs to be a separate
* method. Will be a separate page with a confirmation message to let
* the user know their Quote request has been received.
*/
#PostMapping("/newquote")
public String submitQuoteRequest(#ModelAttribute QuoteRequest formBean) {
//Check Unit Test for ideal logic
return "newQuoteConfirmation";
}//submitQuoteRequest()
}
Controller 1 Unit Tests:
package com.corplithotel.eventsapp.controller;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.corplithotel.eventsapp.domain.QuoteRequest;
/*Step 2 *Create Unit tests for QuoteRequestController*:
* this tests are assuming
*/
#ExtendWith(MockitoExtension.class)
#WebMvcTest( QuoteRequestController.class)
#TestInstance(Lifecycle.PER_CLASS)
public class QuoteRequestController_UnitTests {
#Mock
private WebApplicationContext wac;
#InjectMocks
private QuoteRequestController qrc;
private MockMvc qrcMockMvc;
#BeforeAll
public void setUp() {
qrcMockMvc = MockMvcBuilders.standaloneSetup(qrc).build();
}//BeforeAll
#Test
#DisplayName("testGetQuoteForm.. beginQuoteRequest().. Expected to pass..")
public void testGetQuoteForm() throws Exception {
//simulate getting a new form for the user to fill in (GET)
qrcMockMvc
.perform(get("/newquote"))
.andExpect(status().is(200))
.andReturn();
}//testGetQuoteForm()
#Test
#DisplayName("testPostQuoteForm().. submitQuoteRequest.. Expected to pass..")
public void testPostQuoteForm() throws Exception {
QuoteRequest aFormBean = new QuoteRequest();
qrcMockMvc
.perform(post("/newquote", aFormBean))
.andExpect(status().isOk())
.andReturn();
}//testGetQuoteForm()
}// QuoteRequestController_UnitTests
Result 1:
Junit Controller 1 Results
Controller 2:
package com.corplithotel.eventsapp.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.corplithotel.eventsapp.domain.QuoteRequest;
/*Step 3 *Creating QuoteRequestManagementController
* This is the controller that the sales team member
* uses to reply to a customer's request for an estimate.
* Sale's Team member can see all the incoming requests.
*
*Controller method's body for Step 3 will be empty, we will unit test
* every method of the Controller first in Step 4.
*/
#Controller
public class QuoteRequestManagementController {
/*
* We will be specifying, parameters, look for a parameter of
* a particular value; or looking for the absence of a parameter
*/
//Specifying: Sale's Team member can see all the incoming requests.
#GetMapping(path = "/quoteRequests")
public String listQuoteRequests() {
return "quoteRequestsList";
}//listRequests()
/*Parameter Of A Specific Value: Narrow down search for different
* types of sales reps. Look for 'eventType' = 'wedding' for sales reps that
* only deal with weddings and only see events associated
* with weddings.
*/
#GetMapping(path = "/quoteRequests", params="eventType=wedding")
public String listWeddingRequests() {
return "quoteWeddingRequestsList";
}//listWeddingRequests()
/*Parameter Of A Specific Value: Narrow down search for different types of sales
reps.
* Look for 'eventType' = 'birthday' for sales reps that
* only deal with weddings and only see events associated
* with weddings.
*/
#GetMapping(path = "/quoteRequests", params="eventType=birthday")
public String listBirthdayRequests() {
return "quoteBirthdayRequestsList";
}//listBirthdayRequests()
/*
* Look for 'eventType' parameter regardless of its value
*/
#GetMapping(path = "/quoteRequests", params="eventType")
public String listAllEventTypeRequests() {
return "quoteAllEventTypeRequestList";
}//listAllEventTypeRequests()
/*
* Absence of a parameter: Look for requests with no 'eventType' parameter
*/
#GetMapping(path = "/quoteRequests", params="!eventType")
public String listNoneEventTypeRequests() {
return "quoteNoneEventTypeRequestsList";
}//listNoneEventTypeRequests()
/*
* Specifying: Create another mapping for a sales rep to drill down
* from what I see in a list and pick one particular quote
* request. We will accomplish this by providing each
* quote request a unique quoteID using #PathVariable
*/
#GetMapping("/quoteRequests/{quoteID}")
public String viewQuoteRequest(#PathVariable int quoteID) {
//refer to quoteID in my implementation
return "quoteRequestsDetails";
}//viewQuoteRequest()
/*
*For this scenario lets say a sales rep is in a particular
* quote and maybe want to add a note, which will require them
* to save the content of the screen. This means we need a
* #PostMapping. The sales rep might want to update the customer
* name, event type, food allergy side note, etc.
*
*Once they hit 'save', all the data will come in and be accessible
* through #ModelAttribute and we can reference the Model Attribute in
* the method signature. So as we implement the logic in the controller
* we get to use a Model Bean and pull in all the updated data
* and ultimately save the data somewhere.
*/
#PostMapping ("/quoteUpdateDetails")
public String updateQuoteRequest(
#ModelAttribute("quoteRequest") QuoteRequest quoteRequest) {
//implement a save of all the form bean information
return "quoteUpdateDetails";
}//updateQuoteRequest()
}
Controller 2 Unit Test:
package com.corplithotel.eventsapp.controller;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;
import
org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import com.corplithotel.eventsapp.domain.QuoteRequest;
#ExtendWith(MockitoExtension.class)
#WebMvcTest( QuoteRequestManagementController.class)
#TestInstance(Lifecycle.PER_CLASS)
class QuoteRequestManagementController_UnitTests {
#Mock
private WebApplicationContext wac;
#InjectMocks
private QuoteRequestManagementController qrmc;
private MockMvc qrmcMockMvc;
#BeforeAll
public void setUp() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/");
viewResolver.setSuffix(".html");
qrmcMockMvc=
MockMvcBuilders.standaloneSetup(qrmc)
.setViewResolvers(viewResolver).build();
}//BeforeAll
#Test
#DisplayName("testListQuoteRequests().. Test should pass")
void testlistQuoteRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests"))
.andExpect(status().is(200))
.andReturn();
}//testlistRequests()
#Test
#DisplayName("testListWeddingRequests() .. Parameter Of A Specific Value Test1.. Test should pass")
void testlistWeddingRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?eventType=wedding"))
.andExpect(status().is(200))
.andReturn();
}//testlistWeddingRequests()
#Test
#DisplayName("testListBirthdayRequests() .. Parameter Of A Specific Value Test2.. Test should pass")
void testlistBirthdayRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?eventType=birthday"))
.andExpect(status().is(200))
.andReturn();
}//testlistBirthdayRequests()
#Test
#DisplayName("testListAllEventsRequests() .. Parameter with no specified value.. Test should pass")
void testlistAllEventsRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?eventType"))
.andExpect(status().is(200))
.andReturn();
}//testlistBirthdayRequests()
#Test
#DisplayName("testNoneEventTypeRequests() .. no parameter .. Test should pass")
void testNoneEventTypeRequests() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests?!eventType"))
.andExpect(status().is(200))
.andReturn();
}//testlistBirthdayRequests()
#Test
#DisplayName("testViewQuoteRequest().. by 'quoteID'.. Test should pass")
void testViewQuoteRequest() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests/{quoteID}", 4))
.andExpect(status().is(200))
.andReturn();
}//testViewQuoteRequest()
#Test
#DisplayName("test2ViewQuoteRequest().. by 'quoteID'.. Test should pass")
void tes2tViewQuoteRequest() throws Exception {
qrmcMockMvc
.perform(get("/quoteRequests/{quoteID}", 415))
.andExpect(status().is(200))
.andReturn();
}//testViewQuoteRequest()
#Test
void testupdateQuoteRequest() throws Exception {
MockHttpServletRequestBuilder updateDetails = post("/quoteUpdateDetails")
.param("customer", "Joe")
.param("age", "12")
.param("budget", "$1209")
.param("eventType", "wedding")
.param("foodAllergies", "fish")
.flashAttr("quoteRequest", new QuoteRequest());
qrmcMockMvc
.perform( updateDetails)
.andExpect(status().is(200));
}
}

Unable to cast 'NHibernate.Collection.Generic.PersistentGenericSet`1 to System.Collections.Generic.IList`1

I have a domain class:
public class Agencia : IEntity
{
public virtual int Id { get; set; }
public virtual string Nome { get; set; }
public virtual string Identificacao { get; set; }
public virtual IList<Pessoa> Gerentes { get; protected set; }
public Agencia()
{
Gerentes = new List<Pessoa>();
}
public virtual void AddGerente(Pessoa gerente)
{
Gerentes.Add(gerente);
}
public virtual void AddGerentes(params Pessoa[] gerentes)
{
Parallel.ForEach(gerentes, (pessoa) => Gerentes.Add(pessoa));
}
}
public class Pessoa: IEntity
{
public virtual int Id { get; set; }
public virtual string Nome { get; set; }
}
With this convention (defined as set AsSet)
public class AgenciaConvention : IAutoMappingOverride<Agencia>
{
public void Override(AutoMapping<Agencia> mapping)
{
mapping.HasManyToMany(a => a.Gerentes).Cascade.AllDeleteOrphan().AsSet().Not.Inverse();
}
}
When I run this test:
[TestMethod]
[Description("Uma agência tem vários gerêntes")]
public void AgenciaTemVariosGerentes()
{
// Arrange
var fix = new Fixture();
var currentUser = GetLoggedUser();
// Create a List<Pessoa>
var gerentes = fix.Build<Pessoa>()
.With(p => p.Nome)
.With(p => p.CPF)
.With(p => p.CreateBy, currentUser)
.OmitAutoProperties()
.CreateMany<Pessoa>(10).ToList();
// Action
new PersistenceSpecification<Agencia>(Session)
.CheckProperty(p => p.Nome, fix.Create<string>().Truncate(80))
.CheckProperty(p => p.Identificacao, fix.Create<string>().Truncate(10))
.CheckReference(p => p.Regional,
fix.Build<Regional>()
.With(p => p.Nome)
.OmitAutoProperties()
.Create()
, new IDEqualityComparer())
.CheckList(p => p.Gerentes, gerentes, new IDEqualityComparer())
.CheckReference(p => p.CreateBy, currentUser, new IDEqualityComparer())
.VerifyTheMappings(); // Assert
}
How can I test this list?
The collection should be AsSet, it necessary that the Parent and Children fields are PK, FK
Full Error:
Test Name: AgenciaTemVariosGerentes
Test FullName: {OMMITED}.Integration.Test.AgenciaTest.AgenciaTemVariosGerentes
Test Source: {OMMITED}.Integration.Test\AgenciaTest.cs : line 22
Test Outcome: Failed
Test Duration: 0:00:02,4093555
Result Message:
Test method {OMMITED}.Integration.Test.AgenciaTest.AgenciaTemVariosGerentes threw exception:
NHibernate.PropertyAccessException: Invalid Cast (check your mapping for property type mismatches); setter of CreditoImobiliarioBB.Model.Regional ---> System.InvalidCastException: Unable to cast object of type 'NHibernate.Collection.Generic.PersistentGenericSet1[CreditoImobiliarioBB.Model.Pessoa]' to type 'System.Collections.Generic.IList1[CreditoImobiliarioBB.Model.Pessoa]'.
Result StackTrace:
at (Object , Object[] , SetterCallback )
at NHibernate.Bytecode.Lightweight.AccessOptimizer.SetPropertyValues(Object target, Object[] values)
at NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer(Object entity, Object[] values)
--- End of inner exception stack trace ---
at NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer(Object entity, Object[] values)
at NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValues(Object entity, Object[] values)
at NHibernate.Persister.Entity.AbstractEntityPersister.SetPropertyValues(Object obj, Object[] values, EntityMode entityMode)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSaveOrReplicate(Object entity, EntityKey key, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.PerformSave(Object entity, Object id, IEntityPersister persister, Boolean useIdentityColumn, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.AbstractSaveEventListener.SaveWithGeneratedId(Object entity, String entityName, Object anything, IEventSource source, Boolean requiresImmediateIdAccess)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveEventListener.SaveWithGeneratedOrRequestedId(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.EntityIsTransient(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveEventListener.PerformSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Event.Default.DefaultSaveOrUpdateEventListener.OnSaveOrUpdate(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
at FluentNHibernate.Testing.PersistenceSpecification1.TransactionalSave(Object propertyValue)
at FluentNHibernate.Testing.Values.ReferenceProperty2.HasRegistered(PersistenceSpecification1 specification)
at FluentNHibernate.Testing.PersistenceSpecification1.RegisterCheckedProperty(Property1 property, IEqualityComparer equalityComparer)
at FluentNHibernate.Testing.PersistenceSpecificationExtensions.CheckReference[T](PersistenceSpecification1 spec, Expression`1 expression, Object propertyValue, IEqualityComparer propertyComparer)
at CreditoImobiliarioBB.Repository.Integration.Test.AgenciaTest.AgenciaTemVariosGerentes() in {OMMITED}.Integration.Test\AgenciaTest.cs:line 27
Thanks.
Sets don't implement IList<T>.
Define your properties as ICollection<T> instead.