Serialization/Deserialization. trying to write and read from a file - c#

i'm trying to read and write to a file.
for the first part, writing list of objects to a binary file, i succeed to do so using serialization.
the problem was when i tried to read the back from the file to a list of objects, using deserialize. every time that i'm running the solution i get a runtime error.
the code:
using System;
using System.IO;
using MileStone1Fin.LogicLayer;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Newtonsoft.Json;
namespace MileStone1Fin.PersistentLayer
{
public class UserHandler
{
public UserHandler()
{
}
public void addNewUser(List<User> users)
{
Stream myFileStream = File.Create("UsersData.bin");
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(myFileStream, users);
Console.WriteLine("5535");
myFileStream.Close();
List<User> newUsers1 = null;
if (File.Exists("UsersData.bin"))
{
Stream myFileStream1 = File.OpenRead("UsersData.bin");
BinaryFormatter bf1 = new BinaryFormatter();
newUsers1 = (List<User>)bf1.Deserialize(myFileStream1);//this line marked as the problem one according to visual studio
myFileStream1.Close();
}
}
}
}
Line 38 is the problematic line
The code that made the call:
public void registration(String nickname, String groupID)
{
if(checkUserValidity(nickname, groupID))
{
User newUser = new User(nickname, groupID);
users.Add(newUser);
userHandler.addNewUser(users);
User class:
System.Reflection.TargetInvocationExeption - the runtime error
using System;
using Newtonsoft.Json;
using MileStoneClient.CommunicationLayer;
using MileStone1Fin.PersistentLayer;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace MileStone1Fin.LogicLayer
{
///<summary>
/// This class taking care for the functionallity of the user objects.
/// The user class will be part of the Logic layer
/// </summary>
[Serializable()]
public class User : ISerializable
{
private static UserHandler userHandler = new UserHandler();
private String nickname { get; set; }
private String groupID { get; set; }
private bool status { get; set; }
DateTime lastSeen { get; set; }
public User(String nickname, String groupID) //The User class constructor
{
this.nickname = nickname;
this.groupID = groupID;
this.lastSeen = DateTime.Now;
this.status = false;
}
/*public Message send(String msg) //Creates a Imessage object, return a Message object contains GUID, time, user
{ //information, message body
IMessage message = Communication.Instance.Send(ChatRoom.url, this.groupID, this.nickname, msg);//sends the neccesary details to the server
return new Message(message);
}*/
public void logout()
{
this.status = false;
//Console.WriteLine(this.nickname + "You were disconnected from the server");
lastSeen = DateTime.Now;
//Console.WriteLine(lastSeen);
//need to write into file
}
private bool isOnline()
{
return this.status;
}
public void lastSeenDate()
{
if (isOnline())
Console.WriteLine("Online now");
else
Console.WriteLine(lastSeen.ToString());
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Nickname", nickname);
info.AddValue("GroupId", groupID);
info.AddValue("LastSeen", lastSeen);
info.AddValue("Status", status);
}
public User(SerializationInfo info, StreamingContext context)
{
nickname = (string)info.GetValue("Name", typeof(string));
groupID = (string)info.GetValue("GroupId", typeof(string));
lastSeen = (DateTime)info.GetValue("LastSeen", typeof(DateTime));
status = (Boolean)info.GetValue("Status", typeof(Boolean));
}
}
}

The issue is how you are naming things with serialization, you are serializing Nickname and trying to read it out as Name. The safest thing to do is to get the name right from the property:
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(nickname), nickname);
info.AddValue(nameof(groupID), groupID);
info.AddValue(nameof(lastSeen), lastSeen);
info.AddValue(nameof(status), status);
}
public User(SerializationInfo info, StreamingContext context)
{
nickname = (string)info.GetValue(nameof(nickname), typeof(string));
groupID = (string)info.GetValue(nameof(groupID), typeof(string));
lastSeen = (DateTime)info.GetValue(nameof(lastSeen), typeof(DateTime));
status = (Boolean)info.GetValue(nameof(status), typeof(Boolean));
}
}
Which has the added benefit of when you rename your properties (to the .NET standard), it will automatically rename your code as well. This can cause issues if you rename something and then try to load an old file, so be careful, but at least this way you don't have magic strings floating around in your code. You can avoid the above problem by writing version information to the serialization stream and deserializing based on a version.

Related

Saving objects with constructor to xml

Hello everyone i need some help what do i need to change in this code to be able to serialize (save) this file?
I'm trying to create a inventory file that will save crafted items (this one in particular saves crafted components) i have a almost identical code for saving crafted weapons. Searching the net i couldn't find a way to serialize this object because it uses parameters, but i need to add data into that list and save it. Is there a way around it ? if yes how ? THANK YOU !
the error im getting is
InvalidOperationException: ComponentDB.ItemEntry cannot be serialized because it does not have a parameterless constructor.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System;
using System.Linq;
public class ComponentDB : MonoBehaviour
{
public ItemDatabase itemDB;
public string Slot;
public static ComponentDB ins;
void Awake()
{
ins = this;
Slot = "Slot1";
}
public void SaveItems()
{
XmlSerializer serializer = new XmlSerializer(typeof(ItemDatabase));
FileStream stream = new FileStream(Application.dataPath + "/StreamingAssets/Save/" + Slot + "CraftedComp.xml", FileMode.Create);
serializer.Serialize(stream, itemDB);
stream.Close();
}
public void LoadItems()
{
XmlSerializer serializer = new XmlSerializer(typeof(ItemDatabase));
FileStream stream = new FileStream(Application.dataPath + "/StreamingAssets/Save/" + Slot + "CraftedComp.xml", FileMode.Open);
itemDB = serializer.Deserialize(stream) as ItemDatabase;
stream.Close();
}
[Serializable]
public class ItemEntry
{
public string Name;
public string Data;
public int Amount;
public ItemEntry(string iName, string idata, int iAmount)
{
Name = iName;
Data = idata;
Amount = iAmount;
}
}
[Serializable]
public class ItemDatabase
{
public List<ItemEntry> list = new List<ItemEntry>();
}
public void ManageItemsInv(string input_name, string input_list, int input_amount)
{
ins.itemDB.list.Add(new ItemEntry(input_name, input_list, input_amount));
}
}
Your code are almost OK, except: you missed a parameterless constructor for ItemEntry, then you could not serialize it.
Solution is quite simple: create a parameterless constructor in ItemEntry class:
[Serializable]
public class ItemEntry
{
public string Name;
public string Data;
public int Amount;
//parameterless constructor for XmlSerializer
public ItemEntry()
{
}
public ItemEntry(string iName, string idata, int iAmount)
{
Name = iName;
Data = idata;
Amount = iAmount;
}
}

EF Core - Add new tables to database During Runtime

I have an asp.net core project which needs to be able to support plugins at runtime, and as a consequence, I need to generate database tables based on what has been plugged in. The plugins are each divided in separate projects and they have have their own DbContext class. The plugins to be used are not known during compile-time, only at runtime.
Now in EF Core I thought that there would be a method like "UpdateDatabase" where you can just add tables to the existing database, but I was wrong. Is there a way to accomplish this? I was able to generate a separate database for each of the plugins, but that wasn't quite what I had in mind..I needed all tables in one database.
Here's the code for the "HRContext" plugin:
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Plugins.HR.Models.Entities;
namespace Plugins.HR.Contexts
{
public class HrContext : DbContext
{
public HrContext()
{
}
public HrContext(DbContextOptions<HrContext> contextOptions) : base(contextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("HR");
base.OnModelCreating(modelBuilder);
}
public DbSet<Address> Address { get; set; }
public DbSet<Attendance> Attendance { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<JobTitle> JobTitles { get; set; }
}
}
Here's another piece of code for the "CoreContext" plugin:
using System;
using System.Collections.Generic;
using System.Text;
using Core.Data.Models;
using Microsoft.EntityFrameworkCore;
namespace Core.Data.Contexts
{
public class CoreContext : DbContext
{
public CoreContext()
{
}
public CoreContext(DbContextOptions<CoreContext> contextOptions) : base(contextOptions)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Core");
base.OnModelCreating(modelBuilder);
}
public DbSet<Test> Tests { get; set; }
}
}
My ConfigureServices method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<CoreContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.Core; integrated security = true;"))
.AddDbContext<HrContext>(options => options.UseSqlServer("Data source = localhost; initial catalog = Company.HR; integrated security = true;"));
// Add framework services.
services.AddMvc();
}
If I try to change the connection string to be the same, sooner or later I will get an error that says that the table for one plugin does not exist. I tried "EnsureCreated" but that didn't work too.
I had the same issue. See my solution on GitHub a few days ago, here: EF Core Issue #9238
What you need is something like the following:
// Using an interface, so that we can swap out the implementation to support PG or MySQL, etc if we wish...
public interface IEntityFrameworkHelper
{
void EnsureTables<TContext>(TContext context)
where TContext : DbContext;
}
// Default implementation (SQL Server)
public class SqlEntityFrameworkHelper : IEntityFrameworkHelper
{
public void EnsureTables<TContext>(TContext context)
where TContext : DbContext
{
string script = context.Database.GenerateCreateScript(); // See issue #2943 for this extension method
if (!string.IsNullOrEmpty(script))
{
try
{
var connection = context.Database.GetDbConnection();
bool isConnectionClosed = connection.State == ConnectionState.Closed;
if (isConnectionClosed)
{
connection.Open();
}
var existingTableNames = new List<string>();
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT table_name from INFORMATION_SCHEMA.TABLES WHERE table_type = 'base table'";
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
existingTableNames.Add(reader.GetString(0).ToLowerInvariant());
}
}
}
var split = script.Split(new[] { "CREATE TABLE " }, StringSplitOptions.RemoveEmptyEntries);
foreach (string sql in split)
{
var tableName = sql.Substring(0, sql.IndexOf("(", StringComparison.OrdinalIgnoreCase));
tableName = tableName.Split('.').Last();
tableName = tableName.Trim().TrimStart('[').TrimEnd(']').ToLowerInvariant();
if (existingTableNames.Contains(tableName))
{
continue;
}
try
{
using (var createCommand = connection.CreateCommand())
{
createCommand.CommandText = "CREATE TABLE " + sql.Substring(0, sql.LastIndexOf(";"));
createCommand.ExecuteNonQuery();
}
}
catch (Exception)
{
// Ignore
}
}
if (isConnectionClosed)
{
connection.Close();
}
}
catch (Exception)
{
// Ignore
}
}
}
}
Then at the end of Startup.Configure(), I resolve an IEntityFrameworkHelper instance and use that with an instance of DbContext to call EnsureTables().
One issue is I need to still account for the parts of the script which are not CREATE TABLE statements. For example, the CREATE INDEX statements.
I requested they give us a clean solution, for example: add a CreateTable<TEntity>() method to IRelationalDatabaseCreator. Not holding my breath for that though...
EDIT
I forgot to post the code for GenerateCreateScript(). See below:
using System.Text;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
public static class DatabaseFacadeExtensions
{
public static string GenerateCreateScript(this DatabaseFacade database)
{
var model = database.GetService<IModel>();
var migrationsModelDiffer = database.GetService<IMigrationsModelDiffer>();
var migrationsSqlGenerator = database.GetService<IMigrationsSqlGenerator>();
var sqlGenerationHelper = database.GetService<ISqlGenerationHelper>();
var operations = migrationsModelDiffer.GetDifferences(null, model);
var commands = migrationsSqlGenerator.Generate(operations, model);
var stringBuilder = new StringBuilder();
foreach (var command in commands)
{
stringBuilder
.Append(command.CommandText)
.AppendLine(sqlGenerationHelper.BatchTerminator);
}
return stringBuilder.ToString();
}
}
It's based on the code found here: EF Core Issue #2943

Generate mock XML file from C# class hierachy

In order to generate UBL-order documents in XML, I have created 44 classes in C# using Xml.Serialization. The class consist of a root class "OrderType" which contains a lot of properties (classes), which again contains more properties.
In order to test that the classes always will build a XML document that will pass a validation. I want a XML file containing all the possible nodes (at least once) the hierarchy of Classes/Properties can build.
A very reduced code example:
[XmlRootAttribute]
[XmlTypeAttribute]
public class OrderType
{
public DeliveryType Delivery { get; set; }
//+ 50 more properties
public OrderType(){}
}
[XmlTypeAttribute]
public class DeliveryType
{
public QuantityType Quantity { get; set; }
//+ 10 more properties
public DeliveryType (){}
}
I have already tried to initialise some properties in some of the constructors and it works fine, but this method would take a whole week to finish.
So! Is there a smart an quick method to generate a Mock XML document with all properties initialized?
It's ok that the outer nodes just are defined e.g.:
< Code />
Well, sometimes you have to do it on your own:
using System;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
namespace extanfjTest
{
class Program
{
static void Main(string[] args)
{
OrderType ouo = new OrderType(DateTime.Now);
SetProperty(ouo);
XmlSerializer ser = new XmlSerializer(typeof(OrderType));
FileStream fs = new FileStream("C:/Projects/group.xml", FileMode.Create);
ser.Serialize(fs, ouo);
fs.Close();
}
public static void SetProperty(object _object)
{
if (_object == null)
{ return; }
foreach (PropertyInfo prop in _object.GetType().GetProperties())
{
if ("SemlerServices.OIOUBL.dll" != prop.PropertyType.Module.Name)
{ continue; }
if (prop.PropertyType.IsArray)
{
var instance = Activator.CreateInstance(prop.PropertyType.GetElementType());
Array _array = Array.CreateInstance(prop.PropertyType.GetElementType(), 1);
_array.SetValue(instance, 0);
prop.SetValue(_object, _array, null);
SetProperty(instance);
}
else
{
var instance = Activator.CreateInstance(prop.PropertyType);
prop.SetValue(_object, instance, null);
SetProperty(instance);
}
}
}
}
}

Extract method in C# to use throughout project

Forgive the lengthy code here, and I also realise this may be a very basic fundamental question for any object-oriented developer, but I'm a front-end developer in at the deep end with .NET and trying to learn about classes and methods with an actual example. I've read resources to explain this stuff but immediately get stuck with the complexities of real-world code.
Basically I have a bunch of methods for adding comments to a web page and manipulating the status (marking as spam, deleting etc). Many of these methods call an 'EmailNotification' method, which sends an email to an administrator at each stage. It works great.
However, I'd like to use the 'EmailNotification' method elsewhere in the project, calling it from a different .cs file. When I try to do this it doesn't recognise the method because (I think!?) it's not a public static method.
Can anyone explain to me how to extract the EmailNotification method so that I can use it in different places around the code? I have tried creating a new class with this method inside it, but I just can't get it to work.
using System;
using System.Net.Mail;
namespace UComment.Domain
{
public class Comment
{
public delegate void CommentCreatedEventHandler(Comment sender, EventArgs e);
public delegate void CommentDeletedEventHandler(Comment sender, EventArgs e);
public delegate void CommentSpamEventHandler(Comment sender, EventArgs e);
public delegate void CommentApprovedEventHandler(Comment sender, EventArgs e);
public static event CommentCreatedEventHandler CommentCreated;
public static event CommentDeletedEventHandler CommentDeleted;
public static event CommentSpamEventHandler CommentSpam;
public static event CommentApprovedEventHandler CommentApproved;
protected virtual void OnCommentCreated(EventArgs e)
{
if (CommentCreated != null) CommentCreated(this, e);
}
protected virtual void OnCommentSpam(EventArgs e)
{
if (CommentSpam != null) CommentSpam(this, e);
}
protected virtual void OnCommentApproved(EventArgs e)
{
if (CommentApproved != null) CommentApproved(this, e);
}
protected virtual void OnCommentDelete(EventArgs e)
{
if (CommentDeleted != null) CommentDeleted(this, e);
}
public int Id { get; set; }
public int ParentNodeId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Website { get; set; }
public bool Spam { get; set; }
public bool Approved { get; set; }
public DateTime Created { get; set; }
public string CommenText { get; set; }
public int StatusId { get; set; }
public Comment(int id)
{
Id = id;
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
var reader = sqlHelper.ExecuteReader("select * from Comment where id = #id",
sqlHelper.CreateParameter("#id", id));
if(!reader.HasRecords) throw new Exception(string.Format("Comment with id {0} was not found", id));
reader.Read();
Name = reader.GetString("name");
ParentNodeId = reader.GetInt("nodeid");
Email = reader.GetString("email");
Website = reader.GetString("website");
Approved = reader.GetBoolean("approved");
Spam = reader.GetBoolean("Spam");
Created = reader.GetDateTime("created");
CommenText = reader.GetString("comment");
StatusId = reader.GetInt("statusid");
}
private Comment()
{
}
/// <summary>
/// Set as approved, mark as Not Spam - ignore HAM status
/// </summary>
public void MarkAsApproved()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set approved = 1, spam = 0, statusid = 2 where id = #id",
sqlHelper.CreateParameter("#id", Id));
OnCommentApproved(EventArgs.Empty);
// Send approval email
EmailNotification(1);
}
/// <summary>
/// Remove approval status. Ignore Spam and Ham states
/// </summary>
public void MarkAsNotApproved()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set approved = 0, statusid = 3 where id = #id",
sqlHelper.CreateParameter("#id", Id));
OnCommentApproved(EventArgs.Empty);
// Send rejection email
EmailNotification(2);
}
/// <summary>
/// Spam cannot be ham or approved
/// </summary>
public void MarkAsSpam()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set spam = 1, ham = 0, approved = 0, statusid = 3 where id = #id",
sqlHelper.CreateParameter("#id", Id));
OnCommentSpam(EventArgs.Empty);
// No email notification required - spammer not worthy of a reason for rejection
}
/// <summary>
/// Ham is "not spam" - approved comments from Akismet.
/// </summary>
public void MarkAsHam()
{
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery(
"update comment set spam = 0, ham = 1 where id = #id",
sqlHelper.CreateParameter("#id", Id));
// No email notification required, simply marking spam as ham
}
public void Delete()
{
if (Id < 1) return;
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery("delete from comment where id = #id", sqlHelper.CreateParameter("#id", Id));
Id = -1;
OnCommentDelete(EventArgs.Empty);
// Permanent deletion
}
public void Reject()
{
if (Id < 1) return;
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
sqlHelper.ExecuteNonQuery("update comment set statusid = 3 where id = #id", sqlHelper.CreateParameter("#id", Id));
//Id = -1;
//OnCommentDelete(EventArgs.Empty);
// Send rejection email
EmailNotification(2);
}
public static Comment MakeNew(int parentNodeId, string name, string email, string website, bool approved, bool spam, DateTime created, string commentText, int statusId)
{
var c = new Comment
{
ParentNodeId = parentNodeId,
Name = name,
Email = email,
Website = website,
Approved = approved,
Spam = spam,
Created = created,
CommenText = commentText,
StatusId = statusId
};
var sqlHelper = DataLayerHelper.CreateSqlHelper(cms.GlobalSettings.DbDSN);
c.Id = sqlHelper.ExecuteScalar<int>(
#"insert into Comment(mainid,nodeid,name,email,website,comment,approved,spam,created,statusid)
values(#mainid,#nodeid,#name,#email,#website,#comment,#approved,#spam,#created,#statusid)",
sqlHelper.CreateParameter("#mainid", -1),
sqlHelper.CreateParameter("#nodeid", c.ParentNodeId),
sqlHelper.CreateParameter("#name", c.Name),
sqlHelper.CreateParameter("#email", c.Email),
sqlHelper.CreateParameter("#website", c.Website),
sqlHelper.CreateParameter("#comment", c.CommenText),
sqlHelper.CreateParameter("#approved", c.Approved),
sqlHelper.CreateParameter("#spam", c.Spam),
sqlHelper.CreateParameter("#created", c.Created),
sqlHelper.CreateParameter("#statusid", c.StatusId));
c.OnCommentCreated(EventArgs.Empty);
if (c.Spam)
{
c.OnCommentSpam(EventArgs.Empty);
}
if (c.Approved)
{
c.OnCommentApproved(EventArgs.Empty);
}
return c;
}
public override string ToString()
{
return #"ParentNodeId " + ParentNodeId + #"
Name " + Name + #"
Email " + Email + #"
Website " + Website + #"
Approved " + Approved + #"
Spam " + Spam + #"
Created "+ Created + #"
CommenText " + CommenText + Environment.NewLine;
}
/// <summary>
/// Send email notification
/// </summary>
public void EmailNotification(int notificationType)
{
var uCommentAdminEmail = Config.GetUCommentSetting("uCommentAdminEmail");
MailAddress to = null;
MailAddress from = new MailAddress(uCommentAdminEmail);
string subject = null;
string body = null;
switch (notificationType)
{
case 1:
// Comment approved
to = new MailAddress("me#mydomain.com");
subject = "Comment approved";
body = #"The comment you posted has been approved";
break;
case 2:
// Comment rejected
to = new MailAddress("me#mydomain.com");
subject = "Comment rejected";
body = #"The comment you posted has been rejected";
break;
}
MailMessage message = new MailMessage(from, to);
message.Subject = subject;
message.Body = body;
SmtpClient client = new SmtpClient();
try
{
client.Send(message);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught in EmailNotification: {0}", ex.ToString());
}
finally
{
//
}
}
}
}
Thanks for any pointers folks!
You can:
Extract it to a static method on static class
Create a singleton class that has an instance method
Create a class and interface for MessageSender and use DI to inject it where it's needed.
It depends on the size of the project: for small ones 1. may be enough, for big and complex (and if you have DI in place) 3 would be required.
What you have here is a public method, but because it's not declared as static (public static void EmailNotification...), it cannot be used without creating an instance of the class that it lives in.
using System;
namespace UComment.Domain
{
public class MyOtherClass
{
public void MyMethod()
{
Comment c = new Comment();
c.EmailNotification(1);
}
}
}
You could declare the method static which would let you call it like this:
using System;
namespace UComment.Domain
{
public class MyOtherClass
{
public void MyMethod()
{
Comment.EmailNotification(1);
}
}
}
If you're trying to use it from a different namespace then you would need to include the namespace either by a using statement or by specifying the full namespace inline.
using System;
using UComment.Domain;
namespace UComment.OtherNamespace
{
public class MyOtherClass
{
public void MyMethod()
{
Comment c = new Comment();
c.EmailNotification(1);
}
}
}
Or
using System;
namespace UComment.OtherNamespace
{
public class MyOtherClass
{
public void MyMethod()
{
UComment.Domain.Comment c = new UComment.Domain.Comment();
c.EmailNotification(1);
}
}
}
You are correct in thinking that if you wish to make this a common method, it should independent of the Comment class. The same limitations that I've just described apply to doing that. In addition, you'll have to make sure that any appropriate using statements are on the new class and that the dependencies within the EmailNotification are accounted for as well.
Your class makes too many things!
Split it in different types, each one has to solve only one type of problem according to Separation of concerns.
Same thing for the email sending, create an EmailSender class (or another name) and centralize the Send method there.
You can also create an interface (e.g. ISmtpClientFactory) to pass to the EmailSender class to abstract the concrete system to send emails and improve the testing experience.
Only on the production environment you really send emails, in the test environment you can use a fake factory to simulate the sending.
public class EmailSender
{
private readonly ISmtpClientFactory factory;
public EmailSender(ISmtpClientFactory factory)
{
this.factory = factory;
}
public void Send(MailMessage message)
{
using (var client = factory.Create())
{
using (message)
{
client.Send(message);
}
}
}
}
new EmailSender(new SmtpClientFactory()).Send(AdviceMessageFactory.Create(...));
You could put it in it's own class (like you already tried) and make the method static.
If this new class was EmailHelper, you would call the method like so:
EmailHelper.EmailNotification(1);
Depending on the namespace of the new class, you may also need a using statement at the top of every file you use it in.
It doesn't look like It should cause any issue If you create a (public) class and have that method in it. That method should accept all the properties it needs to send an email. You can create an instance of that class and call that method.

How to pass custom data to deserialization function

I'm doing some C# IO work and I want to import/export data from a few classes.
After looking for a while, it seems serialization is pretty close to what I want.
However, there is a problem. I have a XML file that describes members of a certain class(which I will refer as ValueWithId), which are aggregated in a class which I will call CollectionOfValuesWithId for the purposes of this question.
ValueWithId is a class that contains a string member called ShortName, which is unique. There is only one ShortName per ValueWithId and all ValueWithId have a non-null ShortName. CollectionOfValuesWithId contains a function to find the ValueWithId with a given ShortName.
When serializing, I do NOT want to store ValueWithId nor CollectionOfValuesWithId in the output file. Instead, I just want to store the ShortName in the file.
So far, so good. I just need to use SerializationInfo.AddValue("ValueWithId", MyValueWIthId.ShortName).
The problem comes with deserialization. Some googling suggests that to read data from a file one would do this:
public SomeClassThatUsesValueWithId(SerializationInfo info, StreamingContext ctxt)
{
string name = (string)info.GetValue("ValueWithId", typeof(string));
}
However, the string is not enough to recover the ValueWithId instance. I also need the CollectionOfValuesWithId. I want something like this:
public SomeClassThatUsesValueWithId(SerializationInfo info,
StreamingContext ctxt, CollectionOfValuesWithId extraParameter)
In other words, I need to pass extra data to the deserialization constructor. Does anyone know any way to do this or any alternatives?
I figured it out. The important class to do this is StreamingContext.
This class conveniently has a property named Context(which can be set in the constructor parameter additional.
From MSDN:
additional
Type: System.Object
Any additional information to be associated with the
StreamingContext. This information is available to any object that
implements ISerializable or any serialization surrogate. Most users do
not need to set this parameter.
So here is some sample code regarding how to do this(tested on Mono, but I think it should work on Windows):
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public class ValueWithId
{
public string ShortName;
public string ActualValue;
public ValueWithId(string shortName, string actualValue)
{
ShortName = shortName;
ActualValue = actualValue;
}
public override string ToString()
{
return ShortName + "->" + ActualValue;
}
}
public class CollectionOfValuesWithId
{
private IList<ValueWithId> Values = new List<ValueWithId>();
public void AddValue(ValueWithId val)
{
Values.Add(val);
}
public ValueWithId GetValueFromId(string id)
{
foreach (var value in Values)
if (value.ShortName == id)
return value;
return null;
}
}
[Serializable]
public class SomeClassThatUsesValueWithId : ISerializable
{
public ValueWithId Val;
public SomeClassThatUsesValueWithId(ValueWithId val)
{
Val = val;
}
public SomeClassThatUsesValueWithId(SerializationInfo info, StreamingContext ctxt)
{
string valId = (string)info.GetString("Val");
CollectionOfValuesWithId col = ctxt.Context as CollectionOfValuesWithId;
if (col != null)
Val = col.GetValueFromId(valId);
}
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
//Store Val.ShortName instead of Val because we don't want to store the entire object
info.AddValue("Val", Val.ShortName);
}
public override string ToString()
{
return "Content="+Val;
}
}
class MainClass
{
public static void Main(string[] args)
{
CollectionOfValuesWithId col = new CollectionOfValuesWithId();
col.AddValue(new ValueWithId("foo", "bar"));
SomeClassThatUsesValueWithId sc = new SomeClassThatUsesValueWithId(col.GetValueFromId("foo"));
BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File, col));
using (var stream = new FileStream("foo", FileMode.Create))
{
bf.Serialize(stream, sc);
}
col.GetValueFromId("foo").ActualValue = "new value";
using (var stream2 = new FileStream("foo", FileMode.Open))
{
Console.WriteLine(bf.Deserialize(stream2));
}
}
}
The output I get is:
Content=foo->new value
Which is exactly what I wanted.

Categories