问题
I'm trying to serialize the state of my game on Windows Phone 7, and so made a "Save" struct so I could easily convert all the states into an XML doc using DataContractSerializer.
The Save() code worked fine when I just serialized each data member of the Gamestate individually. However this led to a multiple xml root error, so I made the "Save" class to act as the sole root. Now I get a security exception whenever I try and call DataContractSerializer.WriteObject() on anything.
I've been comparing my old and new code but just can't find anything wrong.
I've pasted all my code below in case the problem lies outside the Save() method.
Just look at the Save() method first since
Exception Details:
Stack Trace:
at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type)
at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type)
at System.Runtime.Serialization.DataContract.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type)
at System.Runtime.Serialization.DataContract.GetDataContract(RuntimeTypeHandle typeHandle, Type type, SerializationMode mode)
at System.Runtime.Serialization.DataContract.GetDataContract(RuntimeTypeHandle typeHandle, Type type)
at System.Runtime.Serialization.DataContract.GetDataContract(Type type)
at System.Runtime.Serialization.DataContractSerializer.get_RootContract()
at System.Runtime.Serialization.DataContractSerializer.InternalWriteStartObject(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(Stream stream, Object graph)
at GameState_test.GameState.Save(String filename)
at GameState_test.MainPage.SaveButton_Click(Object sender, RoutedEventArgs e)
at System.Windows.Controls.Primitives.ButtonBase.OnClick()
at System.Windows.Controls.Button.OnClick()
at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)
NEW CODE:
namespace GameState_test
{ //TODO: Find a way to not have this ignored
[DataContract] public class Hazard { [IgnoreDataMember] public Planet CurrentPlanet;}
public class Connections
{
public Connections(List<List<int>> connections = null) { this.cons = connections; }
public bool AreConnected(int Planet1, int Planet2)
{
for (int i = 0; i < cons[Planet1].Count; i++)
if (cons[Planet1][i] == Planet2) return true;
return false;
}
public List<int> this [int index] { get { return cons[index]; } }
internal readonly List<List<int>> cons; //internal so it can be read by serializer
}
[DataContract]
public class GameState
{
public GameState(List<List<int>> connections) { this.Connections = new Connections(connections); }
public GameState(Position pos, List<List<int>> con) { Position = pos; Connections = new Connections(con); }
public GameState(string filename) //load a game
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (!store.DirectoryExists("SavedGames")) store.CreateDirectory("SavedGames");
using (IsolatedStorageFileStream stream = store.OpenFile("SavedGames/" + filename + ".xml", System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(GameStateSave), new List<Type> {typeof(Hazard), typeof(Inventory), typeof(List<List<int>>), typeof(List<Planet>), typeof(Planet) });
GameStateSave Save = (GameStateSave)serializer.ReadObject(stream);
Position position = new Position(Save.planets, Save.connections, Save.playerPosition);
this.Position = position;
this.PlayerInventory = Save.playerInventory;
this.Connections = new Connections(Save.connections);
}
}
}
[DataMember] public readonly Connections Connections;
[DataMember] public Position Position;
[DataMember] public Inventory PlayerInventory;
public void Save(string filename)
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (!store.DirectoryExists("SavedGames")) store.CreateDirectory("SavedGames");
using (IsolatedStorageFileStream stream = store.OpenFile("SavedGames/" + filename + ".xml", System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
{
DataContractSerializer serializer = new DataContractSerializer(typeof (GameStateSave), new List<Type> {typeof(Hazard), typeof(Inventory), typeof(List<List<int>>), typeof(List<Planet>), typeof(Planet)} );
GameStateSave Save = GenerateSave();
serializer.WriteObject(stream, Save);
//NOTE: Even when I comment everything out but this, I still get an exception:
//serializer.WriteObject(stream, this.Position.Player);
}
}
}
private GameStateSave GenerateSave()
{
GameStateSave Save = new GameStateSave();
Save.connections = this.Connections.cons;
Save.playerInventory = this.PlayerInventory;
Save.planets = this.Position.Planets;
Save.playerPosition = this.Position.Player;
return Save;
}
}
[DataContract]
internal struct GameStateSave //only to be used here
{
[DataMember]
public List<List<int>> connections;
[DataMember]
public Inventory playerInventory;
[DataMember]
public List<Planet> planets;
[DataMember]
public int playerPosition;
}
}
OLD CODE:
namespace GameState_test
{
[DataContract] public class Hazard { [IgnoreDataMember] public Planet CurrentPlanet;}
public class Connections
{
public Connections(List<List<int>> connections = null) { this.cons = connections; }
public bool AreConnected(int Planet1, int Planet2)
{
for (int i = 0; i < cons[Planet1].Count; i++)
if (cons[Planet1][i] == Planet2) return true;
return false;
}
public List<int> this [int index] { get { return cons[index]; } }
internal readonly List<List<int>> cons; //internal so it can be read by serializer
}
[DataContract]
public class GameState
{
public GameState(List<List<int>> connections) { this.Connections = new Connections(connections); }
public GameState(Position pos, List<List<int>> con) { Position = pos; Connections = new Connections(con); }
public GameState(string filename)
{ //load a game
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (!store.DirectoryExists("SavedGames")) store.CreateDirectory("SavedGames");
using (IsolatedStorageFileStream stream = store.OpenFile("SavedGames/" + filename + ".xml", System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(Hazard), new List<Type> { typeof(Inventory), typeof(List<List<int>>), typeof(List<Planet>), typeof(Planet) });
List<List<int>> Connections = (List<List<int>>) serializer.ReadObject(stream);
Inventory PlayerInventory = (Inventory) serializer.ReadObject(stream);
List<Planet> Planets = (List<Planet>) serializer.ReadObject(stream);
int PlayerPosition = (int)serializer.ReadObject(stream);
Position position = new Position(Planets, Connections, PlayerPosition);
this.Position = position;
this.PlayerInventory = PlayerInventory;
this.Connections = new Connections(Connections);
}
}
}
[DataMember] public readonly Connections Connections;
[DataMember] public Position Position;
[DataMember] public Inventory PlayerInventory;
public void Save(string filename)
{
using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
{
if (!store.DirectoryExists("SavedGames")) store.CreateDirectory("SavedGames");
using (IsolatedStorageFileStream stream = store.OpenFile("SavedGames/" + filename + ".xml", System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write))
{
DataContractSerializer serializer = new DataContractSerializer(typeof (Hazard), new List<Type> {typeof(Inventory), typeof(List<List<int>>), typeof(List<Planet>), typeof(Planet)} );
serializer.WriteObject(stream, this.Connections.cons);
serializer.WriteObject(stream, this.PlayerInventory);
serializer.WriteObject(stream, this.Position.Planets);
serializer.WriteObject(stream, this.Position.Player);
}
}
}
internal class SerializableGameState //only to be used here
{
public List<List<int>> connections;
public Inventory playerInventory;
public List<Planet> planets;
public int playerPosition;
}
}
}
回答1:
Silverlight code runs on partial trust, which means that it doesn't have full access to objects in the system, including non-public types and members. You're trying to serialize an internal class, which doesn't work as is. Here's the exception which I get when I try to serialize an internal class:
Exception: System.Security.SecurityException: The data contract type 'SL_ButtonAndText.SerializableGameState' is not serializable because it is not public. Making the type public will fix this error. Alternatively, you can make it internal, and use the InternalsVisibleToAttribute attribute on your assembly in order to enable serialization of internal members - see documentation for more details. Be aware that doing so has certain security implications. ---> System.Security.SecurityException: Security error.
at System.Runtime.Serialization.CodeGenerator.BeginMethod(Type returnType, String methodName, Type[] argTypes, Boolean allowPrivateMemberAccess)
at System.Runtime.Serialization.CodeGenerator.BeginMethod(String methodName, Type delegateType, Boolean allowPrivateMemberAccess)
at System.Runtime.Serialization.XmlFormatWriterGenerator.CriticalHelper.GenerateClassWriter(ClassDataContract classContract)
--- End of inner exception stack trace ---
at System.Runtime.Serialization.ClassDataContract.RequiresMemberAccessForWrite(SecurityException securityException, String[] serializationAssemblyPatterns)
at System.Runtime.Serialization.XmlFormatWriterGenerator.CriticalHelper.GenerateClassWriter(ClassDataContract classContract)
at System.Runtime.Serialization.ClassDataContract.get_XmlFormatWriterDelegate()
at System.Runtime.Serialization.ClassDataContract.WriteXmlValue(XmlWriterDelegator xmlWriter, Object obj, XmlObjectSerializerWriteContext context)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWithoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj, RuntimeTypeHandle declaredTypeHandle)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(Stream stream, Object graph)
at SL_ButtonAndText.MainPage.Button_Click(Object sender, RoutedEventArgs e)
The exception message tells exactly what my problem is (is your exception message similar?). One way to solve this would be to make the class public, which would be the simplest solution. Another alternative, as indicated by the error message, would be to explicitly allow the DataContractSerializer
to have visibility to the internal types, by using the [InternalsVisibleTo]
attribute. The code below has the [IVT]
attribute, and adding that I don't see the error anymore.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
[assembly: InternalsVisibleTo("System.Runtime.Serialization")]
namespace SL_ButtonAndText
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
AddToDebug(typeof(DataContractSerializer).Assembly.FullName);
MemoryStream ms = new MemoryStream();
DataContractSerializer dcs = new DataContractSerializer(typeof(SerializableGameState));
SerializableGameState sgs = new SerializableGameState
{
connections = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5 },
},
playerPosition = 0
};
try
{
dcs.WriteObject(ms, sgs);
AddToDebug("Serialized: {0}", Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Position));
}
catch (Exception ex)
{
AddToDebug("Exception: {0}", ex);
}
}
private void AddToDebug(string text, params object[] args)
{
if (args != null && args.Length > 0) text = string.Format(text, args);
this.Dispatcher.BeginInvoke(() => this.txtDebug.Text = this.txtDebug.Text + text + Environment.NewLine);
}
}
[DataContract]
internal class SerializableGameState
{
[DataMember]
public List<List<int>> connections;
[DataMember]
public int playerPosition;
}
}
回答2:
Carlosfigueira is right, it's because internals are not visible in Silverlight. To fix it, add the following in AssemblyInfo.cs:
[assembly: InternalsVisibleTo("System.Runtime.Serialization, PublicKey="
+ "00240000048000009400000006020000002400005253413100040000010001008D56C76F9E86493"
+ "83049F383C44BE0EC204181822A6C31CF5EB7EF486944D032188EA1D3920763712CCB12D75FB77E"
+ "9811149E6148E5D32FBAAB37611C1878DDC19E20EF135D0CB2CFF2BFEC3D115810C3D9069638FE4"
+ "BE215DBF795861920E5AB6F7DB2E2CEEF136AC23D5DD2BF031700AEC232F6C6B1C785B4305C123B"
+ "37AB")]
[assembly: InternalsVisibleTo("System.ServiceModel.Web, PublicKey="
+ "00240000048000009400000006020000002400005253413100040000010001008D56C76F9E86493"
+ "83049F383C44BE0EC204181822A6C31CF5EB7EF486944D032188EA1D3920763712CCB12D75FB77E"
+ "9811149E6148E5D32FBAAB37611C1878DDC19E20EF135D0CB2CFF2BFEC3D115810C3D9069638FE4"
+ "BE215DBF795861920E5AB6F7DB2E2CEEF136AC23D5DD2BF031700AEC232F6C6B1C785B4305C123B"
+ "37AB")]
[assembly: InternalsVisibleTo("System.Runtime.Serialization.Json, PublicKey="
+ "00240000048000009400000006020000002400005253413100040000010001008D56C76F9E86493"
+ "83049F383C44BE0EC204181822A6C31CF5EB7EF486944D032188EA1D3920763712CCB12D75FB77E"
+ "9811149E6148E5D32FBAAB37611C1878DDC19E20EF135D0CB2CFF2BFEC3D115810C3D9069638FE4"
+ "BE215DBF795861920E5AB6F7DB2E2CEEF136AC23D5DD2BF031700AEC232F6C6B1C785B4305C123B"
+ "37AB")]
Source: http://systemmetaphor.blogspot.fr/2010/04/silverlight-serialization-avoiding.html
回答3:
Check the permission of the files you are saving into or reading from. Maybe it is simple file permission error. Check the folder permission and administrative permissions, etc.
来源:https://stackoverflow.com/questions/10526738/securityexception-when-serializing-with-datacontractserializer