WCF Serialization Server configuration - c#

I have the following ServiceContract:
[ServiceContract(Namespace = "http://WebAdmin.Services.AdministrativeService", Name = "AdministrativeService")]
public interface IAdministrativeService
{
/// <summary>
/// Add the group based on the provided criteria
/// </summary>
/// <example>
/// <code>
/// service.AddWebGroup
/// (new User {
/// SequenceNumber = 1,
/// },
/// new Group
/// {
/// GroupName = "Example Group"
/// },
/// new Application
/// {
/// ApplicationCode = "DS"
/// }
/// );
/// </code>
/// </example>
/// <param name="webUser"><see cref="WebUser"/>The user a</param>
/// <param name="group"><see cref="Group"/></param>
/// <param name="application"><see cref="Application"/></param>
/// <returns><see cref="Group"/></returns>
[OperationContract]
[TransactionFlow(TransactionFlowOption.Mandatory)]
[FaultContract(typeof(ErrorFault))]
[PrincipalPermission(SecurityAction.Demand, Role = "WEB_ADMIN_SVC")]
Group AddWebGroup(WebUser webUser, Group group, Application application);
}
With the following DataContracts:
[DataContract]
public class WebUser
{
[DataMember]
public long? SequenceNumber { get; set; }
[DataMember]
public List<UserRole> Roles { get; set; }
}
[DataContract]
public class UserRole
{
[DataMember]
public long? RoleID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public Application Application { get; set; }
[DataMember]
public Group Group { get; set; }
}
{
[DataContract]
public class Application
{
[DataMember]
public string ApplicationCode { get; set; }
[DataMember]
public string ApplicationName { get; set; }
[DataMember]
public string ContactEmailAddress { get; set; }
[DataMember]
public string DivisionCode { get; set; }
}
[DataContract]
public class Group
{
[DataMember]
public long? GroupID { get; set; }
[DataMember]
public string GroupName { get; set; }
}
With the following implementation:
[ServiceBehavior(
ConcurrencyMode = ConcurrencyMode.Single,
InstanceContextMode = InstanceContextMode.PerSession,
ReleaseServiceInstanceOnTransactionComplete = false,
TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable
)]
public class AdministrativeService : ServiceBase, IAdministrativeService
{
#region Implementation of IAdministrativeService
/// <summary>
/// Add the group based on the provided criteria
/// </summary>
/// <example>
/// <code>
/// service.AddWebGroup
/// (new User {
/// SequenceNumber = 1,
/// },
/// new Group
/// {
/// GroupName = "Example Group"
/// },
/// new Application
/// {
/// ApplicationCode = "DS"
/// }
/// );
/// </code>
/// </example>
/// <param name="webUser"><see cref="WebUser"/>The user adding the group</param>
/// <param name="group"><see cref="Group"/>The Group being added</param>
/// <param name="application"><see cref="Application"/>The associated application</param>
/// <returns><see cref="Group"/> With the ID set</returns>
[OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]
public Group AddWebGroup(WebUser webUser, Group group, Application application)
{
if (webUser == null)
throw new ArgumentException("user");
if (webUser.SequenceNumber == long.MinValue)
throw new ArgumentException("user sequence number");
if (group == null || string.IsNullOrWhiteSpace(group.GroupName))
throw new ArgumentNullException("group");
if (application == null || string.IsNullOrWhiteSpace(application.ApplicationCode))
throw new ArgumentException("application");
var service = Resolve<IWebGroupService>();
var criteria = new GroupCriteriaEntity
{
ApplicationCode = application.ApplicationCode,
Group = new GroupEntity{GroupName = group.GroupName,},
User = new UserEntity{SequenceNumber = webUser.SequenceNumber,}
};
GroupEntity result = null;
try
{
result = service.InsertWebGroup(criteria);
}
catch (Exception ex)
{
ex.Data.Add("result",criteria);
ExceptionPolicy.HandleException(ex, "Service Policy");
}
var groupResult = new Group
{
GroupID = result.SequenceNumber
};
return groupResult;
}
The service model binding is using wsHttpBinding with TransportMessageCredential...
All of this excellent stuff works in a dev environment. Transactions commit and roll back as expected. The user credentials allow and prevent the user from executing the codes and all in all everyone is happy happy happy.
The test environment is supposed to be an identical setup as the dev environment. However, when the proxy client invokes the service I get the following exception:
HandlingInstanceID: 44a7c5ae-17fc-4d14-b55e-1aa53bb156e0
An exception of type 'System.ArgumentException' occurred and was caught.
06/13/2012 15:39:11
Type : System.ArgumentException, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Message : Argument passed in is not serializable.
Parameter name: value
Source : mscorlib
Help link :
ParamName : value
Data : System.Collections.ListDictionaryInternal
TargetSite : Void Add(System.Object, System.Object)
Stack Trace : at System.Collections.ListDictionaryInternal.Add(Object key, Object value)
at Administrative.Service.AdministrativeService.AddWebGroup(WebUser webUser, Group group, Application application)
at SyncInvokeAddWebGroup(Object , Object[] , Object[] )
at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
Any ideas on why this would occur on one machine but not another? Why would I be getting an error that I am passing a non-serializable object when all objects are being passed are objects gnerated in the proxy client and are tagged with DataContract?

It looks like the exception is happening at the exception handler where you do: ex.Data.Add("result",criteria);, but I don't see in your data contracts (the ones you showed) where GroupCriteriaEntity is defined, is that one also serializable?

Related

PartitionKey value must be supplied for this operation in cosmosdb delete operaiton

I am trying to delete a document from Cosmos DB
My code is like this:
public async Task<IHttpActionResult> DeletePartner(string id)
{
var telemetry = new TelemetryClient();
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customers = await CosmosStoreHolder.Instance.CosmosStoreCustomer.Query().Where(x=> x.PartnerId == id).ToListAsync();
var userStore = CosmosStoreHolder.Instance.CosmosStoreUser;
var users = await userStore.Query().Where(x => x.PartnerId == id).ToListAsync(); ;
if (customers.Count> 0 || users.Count>0)
{
return BadRequest("You cant delete partners with existing customers or users");
}
else
{
var result = await CosmosStoreHolder.Instance.CosmosStorePartner.RemoveByIdAsync(id, "/CosmosEntityName");
return Ok(result);
}
}
catch (Exception ex)
{
string guid = Guid.NewGuid().ToString();
var dt = new Dictionary<string, string>
{
{ "Error Lulo: ", guid }
};
telemetry.TrackException(ex, dt);
return BadRequest("Error Lulo: " + guid);
}
}
}
[SharedCosmosCollection("shared")]
public class Partner : ISharedCosmosEntity
{
/// <summary>
/// Partner id
/// </summary>
[JsonProperty("Id")]
public string Id { get; set; }
/// <summary>
/// Partner name
/// </summary>
public string PartnerName { get; set; }
/// <summary>
/// Partner contact name
/// </summary>
public string PartnerContact { get; set; }
/// <summary>
/// Partner contact phone
/// </summary>
public string PartnerPhone { get; set; }
/// <summary>
/// Partner contact Office 365 domain
/// </summary>
public string PartnerDomain { get; set; }
/// <summary>
/// Partner type, silver, gold or platinum
/// </summary>
[ValidEnumValue]
public PartnerType PartnerType { get; set; }
/// <summary>
/// Partner start date
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// Partner end date
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// Parter enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// CosmosEntityname
/// </summary>
[CosmosPartitionKey]
public string CosmosEntityName { get; set; }
}
/// <summary>
/// Partner type Enum
/// </summary>
public enum PartnerType
{
///Silver
Silver,
///Gold
Gold,
///Platinum
Platinum
}
But I got this error:
PartitionKey value must be supplied for this operation
I was trying to send as string "/CosmosEntityName" as second parameter, but it doesnt work
I am using Cosmonaut
You need to use the request options. For example, if your collection is partitioned by CosmosEntityName;
await this.documentClient.DeleteDocumentAsync(productDocument._self, new RequestOptions { PartitionKey = new Microsoft.Azure.Documents.PartitionKey(productDocument.CosmosEntityName) });
EDIT:
Here's what you need with Cosmonaut SDK
You need to provide the partition key value not the partition key
definition when you delete. Your delete request should look like this,
assuming the id is your partition key.
var deleted = await this._cosmonautClient.DeleteDocumentAsync(this._databaseName, collectionName, message.Id, new RequestOptions { PartitionKey = new PartitionKey(message.Id) });
You need to pass the value of the partition key of the element you want to delete as second parameter, not the path and attribute name.
var result = await CosmosStoreHolder.Instance.CosmosStorePartner.RemoveByIdAsync(id, "<partition key value for that id>");
Since the attribute you have defined as PK is CosmosEntityName, you need that attribute's value for that document.

How to show responses example in swagger documenation

I developed asp.net web API and I used swagger to API documentation and consume purposes. I need to show swagger response model sample in swagger documentation as follows
This image I got from the internet
How can I add a response example as above image
My controller as follows
/// <param name="sDate">Start date</param>
/// <param name="eDate">End date</param>
/// <param name="lCode">Location code</param>
/// <param name="page">Page number</param>
/// <param name="pageSize">Page size</param>
[Route("lobbydetail")]
[SwaggerResponse(HttpStatusCode.OK, Type = typeof(ResultOutput<List<LDetailRecord>>))]
[SwaggerResponse(HttpStatusCode.BadRequest, Type = typeof(APIError))]
[SwaggerResponse(HttpStatusCode.InternalServerError, Type = typeof(APIError))]
public IHttpActionResult GetDetails(DateTime sDate, DateTime eDate, string lCode = null, int page = 1, int pageSize = 100)
{
try
{
if (sDate > eDate)
{
return Content(HttpStatusCode.BadRequest, new APIError("400", "Start date is greater than end date."));
}
var tID = Convert.ToInt32(jwtData.GetTokenClaim(TENANT_ID));
return Ok(dataView.GetDetailViewData(tID, sDate, eDate, lCode, page, pageSize));
}
catch (ArgumentException ae)
{
return Content(HttpStatusCode.BadRequest, new APIError("404", "Invalid location code"));
}
catch (Exception ex)
{
Logger.LogErrorEvent(ex);
return Content(HttpStatusCode.InternalServerError, new APIError("500", "Error occurred"));
}
}
My as follows LDetailRecord
public class LDetailRecord
{
public DateTime TDateTime { get; set; }
public dynamic Account { get; set; }
public string LCode { get; set; }
public string LName { get; set; }
public string ConfNumber { get; set; }
public decimal WTime { get; set; }
public decimal AssTime { get; set; }
public List<string> RequestedServices { get; set; }
public string PersonRequested { get; set; }
public string AssistedBy { get; set; }
public string CustomerType { get; set; }
public string CheckedInBy { get; set; }
public string Comments { get; set; }
public string PreferredLanguage { get; set; }
}
In my swagger shows as follows
I'm new to the web api and swagger, please help me, what I did wrong here
The answer by #Mikah-Barnett is not entirely correct when it comes to error responses.
Also, because you're returning a different type when there's an error, use the
[ProducesErrorResponseType(typeof(APIError))]
as well. That will let Swagger know you want a different model when there's a client error.
ProducesErrorResponseTypeAttribute(Type) - Is used for API documentation, but can only define a single error type for all errors which are specified with ProducesResponseTypeAttribute(Int32) attribute.
ProducesResponseTypeAttribute(Type, Int32) - Is used for API documentation when you want to have more detailed granularity over all the different types returned, depending on the response status code
As an example, below is what you could define per endpoint. Even better, common response type attributes can be specified at the controller level, meaning you don't need to duplicate for every endpoint.
[HttpPost]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
[ProducesResponseType(typeof(NewOrderResponse), StatusCodes.Status201Created)]
public async Task<IActionResult> Post([FromBody, Required] NewOrderRequest orderRequest)
You need to explicitly state the return type in your methods. So, instead of
public IHttpActionResult GetDetails(...
use
public IHttpActionResult<LDetailRecord> GetDetails(...
That lets OpenAPI know exactly what you're planning to return and it will then show an example of the model in the UI.
Also, because you're returning a different type when there's an error, use the
[ProducesErrorResponseType(typeof(APIError))]
as well. That will let Swagger know you want a different model when there's a client error.
Here's a good article from MSFT documenting how this works, and below is a more complete example (from that article) showing all the pieces together.
/// <summary>
/// Creates a TodoItem.
/// </summary>
/// <remarks>
/// Sample request:
///
/// POST /Todo
/// {
/// "id": 1,
/// "name": "Item1",
/// "isComplete": true
/// }
///
/// </remarks>
/// <param name="item"></param>
/// <returns>A newly created TodoItem</returns>
/// <response code="201">Returns the newly created item</response>
/// <response code="400">If the item is null</response>
[HttpPost]
[ProducesResponseType(201)]
[ProducesResponseType(400)]
[ProducesErrorResponseType(typeof(APIError))]
public ActionResult<TodoItem> Create(TodoItem item)
{
_context.TodoItems.Add(item);
_context.SaveChanges();
return CreatedAtRoute("GetTodo", new { id = item.Id }, item);
}

c# How to Add Custom Validation Based on Boolean

Here i have a Class Like
Public Class Employee
{
[Required]
public string FName { get; set; }
[Required]
public bool Married { get; set; }
public string WifeName { get; set; }
public string Wife_dOB { get; set; }
}
Here WifeName And Wife_Dob is Required only When Married Is True
Please Help me How can i Resolve this Problem
Im using Here MVC
You can use a custom validation attribute.
You can see bellow a solution that I used a few time ago:
/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
#region Properties
/// <summary>
/// Gets or sets the other property name that will be used during validation.
/// </summary>
/// <value>
/// The other property name.
/// </value>
public string OtherProperty { get; private set; }
/// <summary>
/// Gets or sets the display name of the other property.
/// </summary>
/// <value>
/// The display name of the other property.
/// </value>
public string OtherPropertyDisplayName { get; set; }
/// <summary>
/// Gets or sets the other property value that will be relevant for validation.
/// </summary>
/// <value>
/// The other property value.
/// </value>
public object OtherPropertyValue { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
/// </summary>
/// <value>
/// <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// How this works
/// - true: validated property is required when other property doesn't equal provided value
/// - false: validated property is required when other property matches provided value
/// </remarks>
public bool IsInverted { get; set; }
/// <summary>
/// Gets a value that indicates whether the attribute requires validation context.
/// </summary>
/// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
public override bool RequiresValidationContext
{
get { return true; }
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
/// </summary>
/// <param name="otherProperty">The other property.</param>
/// <param name="otherPropertyValue">The other property value.</param>
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
: base("'{0}' is required because '{1}' has a value {3}'{2}'.")
{
this.OtherProperty = otherProperty;
this.OtherPropertyValue = otherPropertyValue;
this.IsInverted = false;
}
#endregion
/// <summary>
/// Applies formatting to an error message, based on the data field where the error occurred.
/// </summary>
/// <param name="name">The name to include in the formatted message.</param>
/// <returns>
/// An instance of the formatted error message.
/// </returns>
public override string FormatErrorMessage(string name)
{
return string.Format(
CultureInfo.CurrentCulture,
base.ErrorMessageString,
name,
this.OtherPropertyDisplayName ?? this.OtherProperty,
this.OtherPropertyValue,
this.IsInverted ? "other than " : "of ");
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException("validationContext");
}
PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (otherProperty == null)
{
return new ValidationResult(
string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
}
object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);
// check if this value is actually required and validate it
if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
{
if (value == null)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
// additional check for strings so they're not empty
string val = value as string;
if (val != null && val.Trim().Length == 0)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
FluentValidation might be a good choice for your problem. With FluentValidation it would be easy to create an EmployeeValidator class to perform as much or as little validation as needed.
Note: The examples below are written using the standard FluentValidation package since I don't use MVC. There appears to be nuget packages for several versions of MVC in addition to the standard nuget package and your mileage may vary using those implementations.
Here is a very simple example of how to use FluentValidation:
void Main()
{
var emp = new Employee { FName = "John Smith", Married = true };
var val = new EmployeeValidator();
val.ValidateAndThrow(emp);
}
public class Employee
{
public string FName { get; set; }
public bool Married { get; set; }
public string WifeName { get; set; }
public string Wife_dOB { get; set; }
}
public class EmployeeValidator : AbstractValidator<Employee>
{
public EmployeeValidator()
{
RuleFor(e => e.WifeName).NotEmpty().When(e => e.Married);
RuleFor(e => e.Wife_dOB).NotEmpty().When(e => e.Married);
}
}
EDIT
I forgot to include an example of the output from the validation code above.
Example output:
ValidationException: Validation failed:
-- WifeName: 'Wife Name' must not be empty.
-- Wife_dOB: 'Wife_d OB' must not be empty.
An alternative approach, and in my opinion a better one, would be something like this that requires no validation, at least for the Married property:
public class Person
{
public Person(string firstName, string surname, DateTime dateOfBirth)
{
FirstName = firstName;
Surname = surname;
DateOfBirth = dateOfBirth;
}
public string FirstName { get; set; }
public string Surname { get; set; }
public string FullName => $"{FirstName} {Surname}";
public DateTime DateOfBirth { get; }
}
public class Employee : Person
{
public Employee(string firstName, string lastName, DateTime dateOfBirth, DateTime dateOfHire)
: base(firstName, lastName, dateOfBirth)
=> DateOfHire = dateOfHire;
public DateTime DateOfHire { get; }
public Person Spouse { get; set; }
public bool IsMarried => Spouse != null;
}
With this implementation the IsMarried property simply projects whether or not the Spouse property is set. This is purely for convenience but can often be helpful.
Some validation that might make sense for these objects could be the following:
public class PersonValidator : AbstractValidator<IPerson>
{
public PersonValidator()
{
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.Surname).NotEmpty();
RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());
}
}
public class EmployeeValidator : AbstractValidator<IEmployee>
{
private static readonly DateTime CompanyInceptionDate
= DateTime.Today.Subtract(TimeSpan.FromDays(365.25d * 10d));
public EmployeeValidator()
{
// Person rules
RuleFor(p => p.FirstName).NotEmpty();
RuleFor(p => p.Surname).NotEmpty();
RuleFor(p => p.DateOfBirth).SetValidator(new DateOfBirthValidator());
// Employee rules
RuleFor(e => e.DateOfHire).SetValidator(
// Can't be in the future nor older than the company
new DateRangeValidator(CompanyInceptionDate, DateTime.Today));
}
}
// This class really isn't necessary
// The built-in InclusiveBetween/ExclusiveBetween validators would work just as well
public class DateRangeValidator : PropertyValidator
{
protected const double DaysInAYear = 365.25d;
public DateRangeValidator(DateTime from, DateTime to)
: base($"{{PropertyName}} out of range. Expected between {from:yyyy-MM-dd} and {to:yyyy-MM-dd}.")
{
From = from;
To = to;
}
public DateTime From { get; }
public DateTime To { get; }
protected override bool IsValid(PropertyValidatorContext context)
=> context.PropertyValue is DateTime date
? date >= From && date <= To
: false;
}
public class DateOfBirthValidator : DateRangeValidator
{
private static readonly TimeSpan OneHundredAndFiftyYears
= TimeSpan.FromDays(DaysInAYear * 150d);
public DateOfBirthValidator()
// Can't be in the future nor older than 150 years
: base(DateTime.Today.Subtract(OneHundredAndFiftyYears), DateTime.Today) { }
}

Too much recursion when attempting to serialize object

I'm currently testing usage of YML in my application, to provide a more friendly means to editing and configuring e-mail downloads and imports for users.
For testing purposes, I wanted to see if my mocked-up YML is roughly equivalent to what the application would put out.
So, for testing I have this block of code, and it never fails to throw "Too much recursion".
// TEST SERIALIZE DATA
{
var _testDomain = default(Domain);
var _testSecureString = new SecureString();
"testP455w0rd".ToCharArray().ToList().ForEach(_testSecureString.AppendChar);
var _testConfig = new EmailBeamConfig() {
DefaultProtocol = ProtocolType.Imap,
Domains = new List<Domain> {
(_testDomain = new Domain {
Accounts = new List<EmailAccount> {
new EmailAccount {
Domain = _testDomain.DomainName,
InternalUid = "de.test_a_test-account",
Password = _testSecureString,
Username = "test-account#test.de"
},
new EmailAccount {
Domain = _testDomain.DomainName,
InternalUid = "de.test_a_test-account2",
Password = _testSecureString,
Username = "test-account2#test.de"
}
},
DomainName = "de.test",
EmailServer = "192.168.252.172",
UseEncryption = true
})
},
DownloadInterval = new TimeSpan(0, 10, 0),
DownloadRoot = mainSystemConfig.GetString("importRootDir")
};
var _testSerializer = new SerializerBuilder()
.WithNamingConvention(new UnderscoredNamingConvention())
.Build();
var _testSerializedData = _testSerializer.Serialize(_testConfig);
File.WriteAllText(cfgFile.FullName, _testSerializedData);
}
Here is the source to EmailBeamConfig:
internal class EmailBeamConfig {
private string _downloadRoot;
/// <summary>
/// Gets or sets the download root dir.
/// </summary>
public string DownloadRoot {
get => _downloadRoot;
set {
_downloadRoot = value;
DownloadRootObj = new DirectoryInfo(value);
}
}
/// <summary>
/// Gets an object representing the download root.
/// </summary>
public DirectoryInfo DownloadRootObj { get; private set; }
/// <summary>
/// Gets the interval for downloading e-mails.
/// </summary>
public TimeSpan DownloadInterval { get; set; }
/// <summary>
/// Gets or sets the default protocol to be used,
/// if a protocol is not defined for an individual user.
/// </summary>
public ProtocolType DefaultProtocol { get; set; }
public List<Domain> Domains { get; set; }
}
And EmailAccount:
/// <summary>
/// Defines an e-mail account for use in this plugin.
/// </summary>
internal struct EmailAccount {
private string _internalUid;
/// <summary>
/// Gets or sets the username for this e-mail account.
/// (Used for logging in, etc)
/// </summary>
internal string Username { get; set; }
/// <summary>
/// gets or sets the password for this account.
/// </summary>
internal SecureString Password { get; set; }
/// <summary>
/// Gets or sets the internal user identifier for this user account.
/// </summary>
internal string InternalUid {
get => _internalUid;
set {
if (value == "/" || value.Length < 10) {
_internalUid = $"{ Domain }_a_{ Username?.Split('#')[0] }";
} else _internalUid = value;
}
}
/// <summary>
/// Gets or sets the domain this e-mail account is associated with.
/// </summary>
internal string Domain { get; set; }
}
I'm completely oblivious to any source of infinite recursion at this point.
EDIT: Here is the Domain struct declaration:
internal struct Domain {
/// <summary>
/// Gets or sets the name of this domain.
/// </summary>
internal string DomainName { get; set; }
/// <summary>
/// Gets or sets the URL/IP address of the e-mail server.
/// </summary>
internal string EmailServer { get; set; }
/// <summary>
/// Gets or sets a value determining whether to attempt to use an encrypted connection to
/// the e-mail server.
/// </summary>
internal bool UseEncryption { get; set; }
/// <summary>
/// Gets the list of accounts associated with this domain
/// </summary>
internal List<EmailAccount> Accounts { get; set; }
}

Can i JSON serialize a object which contains a list?

I've run into a issue when using JsonConvert.SerializeObject(addToBasketView) as it seems not to like tha list that i pass into it - until i passed in the list it was working fine - are you not able to pass a list?
Here is my code:
controller:
//We need a list of Automation scripts - this was in Apollo but now we need to pass it in teh message
var automationList = AutomationHelper(retailerId, productId);
var addToBasketView = new AddToBasketView
{
Url = retailerProduct.DeepLink,
Password = (password),
Username = (username),
RetailerProductJson = new RetailerProductJson(retailerProduct),
WidgetImpressionId = id,
Quantity = qty,
MessageId = messageId,
AutomationList = automationList
};
// now turn it into a string
var json = JsonConvert.SerializeObject(addToBasketView);
Addtobasketview.cs:
using System;
using System.Collections.Generic;
namespace WidgetData
{
/// <summary>
/// This is the view that we will give to the AddToBasketForm
/// </summary>
[Serializable]
public class AddToBasketView
{
/// <summary>
/// The Username for the retailer
/// </summary>
public String Username { get; set; }
/// <summary>
/// The password for the retailer
/// </summary>
public String Password { get; set; }
/// <summary>
/// The URl of the thing they want added to the site
/// </summary>
public String Url { get; set; }
/// <summary>
/// The retailer product selected - from this I can get the retailer and the product.
/// </summary>
public RetailerProductJson RetailerProductJson { get; set; }
/// <summary>
/// The widget impression id so that we can attach to the addTobaskets table for recording purposes
/// </summary>
public int WidgetImpressionId { get; set; }
/// <summary>
/// set the quantity
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// The MessageId this is so we can identify it when waiting for a response
/// </summary>
public String MessageId { get; set; }
/// <summary>
/// Automation script list
/// </summary>
public List<AutomationStepScript> AutomationList { get; set; }
}
}
Edit:
AutomationStepScript.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace WidgetData
{
/// <summary>
/// Extends the class created by Linq To SQL, so that we can control in our models section the validity of the data
/// </summary>
[MetadataType(typeof(AuotmationStepScriptValidation))]
public partial class AutomationStepScript
{
/// <summary>
/// Override the to string so that we can use in the delete method to capture details of this record before it is deleted
/// </summary>
/// <returns></returns>
public override string ToString()
{
return string.Format("AutomationStepScript Id = {0}, AutomationStepId = {1}, Sort Order = {2}, Description = {3}, Script = {4}, Evaluate = {5}", Id,AutomationStepId,SortOrder,Description,Script,Evaluate);
}
}
/// <summary>
/// Class to validate my AuotmationStepScript
/// </summary>
public class AuotmationStepScriptValidation
{
/// <summary>
/// AutomationId
/// </summary>
[Required(ErrorMessage = "AutomationStepId is a required field")]
[Range(1, int.MaxValue, ErrorMessage = "AutomationStepId must be valid")]
public int AutomationStepId { get; set; }
/// <summary>
/// Display SortOrder
/// </summary>
[Range(0, int.MaxValue, ErrorMessage = "SortOrder must be valid")]
public int SortOrder { get; set; }
/// <summary>
/// Description
/// </summary>
[StringLength(256, ErrorMessage = "Description maximum length is 256", MinimumLength = 1)]
public String Description { get; set; }
/// <summary>
/// Script
/// </summary>
[AllowHtml]
[StringLength(8000, ErrorMessage = "Script maximum length is 8000")]
public String Script { get; set; }
/// <summary>
/// Evaluate
/// </summary>
public int Evaluate { get; set; }
}
}
AutomationHelper (called in teh controller to get the list:
/// <summary>
/// The helper function to fetch the data so that we can get the scripts for each step for this automation (which we will find from the localParams)
/// </summary>
/// <param name="retailerId"> </param>
/// <param name="productId"> </param>
/// <returns>WE return a List of AutomationStepScripts, this object will be selected further with Lambda expression"></returns>
public List<AutomationStepScript> AutomationHelper(int retailerId, int productId)
{
List<AutomationStepScript> automationStepScripts = null;
var automationStepScriptRepository = new AutomationStepScriptRepository();
var productRepository = new ProductRepository();
var retailerCategoryRepository = new RetailerCategoryRepository();
var product = productRepository.GetProduct(productId);
var categoryId = product.CategoryId;
var retailerCategory = retailerCategoryRepository.GetRetailerCategoryByRetailerCategory(retailerId, categoryId);
// DO we have a retailer category?
if (retailerCategory != null)
{
// Yes, without a valid retailer category we cannot possibly find the automation
// Now here we have the RetailerCategory. The next question is Does this RetailerCategory HAVE an automation set up for it
if (retailerCategory.AutomationId != null)
{
// Yes, we have an automation. So lets get all of the Scripts for all of the steps for this automation
// Get All scripts for all steps for this automation.
automationStepScripts = automationStepScriptRepository.GetAllAutomationStepScriptsForAutomation((int)retailerCategory.AutomationId).ToList();
}
}
return automationStepScripts;
}
I am serializinf it as it bacomes teh body of a message which i am using in SQS which i then use later.
Please let me know if you need any more information.
Thanks.

Categories