Silverlight 2 - Adding database records using WCF - c#

I'm creating a simple Silverlight 2 application - a guestbook. I'm using MSSQL as the data source, I've managed to load the data but I can't find out how to add new rows (messages) to the database.
I crawled all the internet but didn't find any working solution. The SCMEssages table has four columns - MessageID, MessageDate, MessageAuthor and MessageText. Right now I have the following code in Service1 class (which implements IService1 interface) (not working though):
public void SaveMessage(SCMessage message)
{
DataClasses1DataContext db=new DataClasses1DataContext();
db.GetTable<SCMessage>().Attach(message);
db.SubmitChanges();
}
In the main class I'm simply calling this method:
private void SendBtn_Click(object sender, RoutedEventArgs e)
{
SCMessage sm = new SCMessage
{
MessageAuthor = NameTB.Text,
MessageDate = DateTime.Now,
MessageText = TextTB.Text
};
newMessages.Add(sm);
ServiceReference1.Service1Client client = new Service1Client();
client.SaveMessageAsync(sm);
}
Could anybody help me? Thanks for any suggestions!

I'm not sure I complete understand the context (like do you control your WCF service and/or your DB). But did you consider ADO.Net Data services? (also known as astoria)
(http://msdn.microsoft.com/en-us/library/cc668792.aspx)
Then you don't need to create a webservice for it, it is already created for you.
Basically it is an easy way to access your data from within Silverlight and even be able to do queries from within silverlight.
There is already a bit of doc in blogs, for example:
A quickstart is here: http://michaelsync.net/2008/01/15/consuming-adonet-data-service-astoria-from-silverlight
How to update data can be seen here: http://michaelsync.net/2008/02/10/crud-operations-in-siverlight-using-adonet-data-service
A complete working example is here: http://www.silverlightdata.com/
Note that in a lot of examples on the web the silverlight proxy is generated using the command line, that is however not needed anymore, you can do it directly from within VS using "add service reference" to your project and pointing it to your ado.net data service
Hope this helps a bit?
Tjipke

Is SCMessage decorated with the [DataContract] attribute or is it [Serializable]? Please provide us with the definition of SCMessage.

SCMessage is name of a Data Class - I created a file from template "Linq to SQL Classes" (.dbml) and drag&dropped the SCMessages table to the Designer. It's decorated with the [DataContract] attribute and I set it's DataContext's Serialiation Mode property to Unidirectional. So the content of the SCMessage class is auto-generated, but here is as least a part of it:
[Table(Name="dbo.SCMessages")]
[DataContract()]
public partial class SCMessage : INotifyPropertyChanging, INotifyPropertyChanged
{
private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
private int _MessageID;
private string _MessageAuthor;
private string _MessageText;
private System.DateTime _MessageDate;
#region Extensibility Method Definitions
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnMessageIDChanging(int value);
partial void OnMessageIDChanged();
partial void OnMessageAuthorChanging(string value);
partial void OnMessageAuthorChanged();
partial void OnMessageTextChanging(string value);
partial void OnMessageTextChanged();
partial void OnMessageDateChanging(System.DateTime value);
partial void OnMessageDateChanged();
#endregion
public SCMessage()
{
this.Initialize();
}
[Column(Storage="_MessageID", AutoSync=AutoSync.OnInsert, DbType="Int NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]
[DataMember(Order=1)]
public int MessageID
{
get
{
return this._MessageID;
}
set
{
if ((this._MessageID != value))
{
this.OnMessageIDChanging(value);
this.SendPropertyChanging();
this._MessageID = value;
this.SendPropertyChanged("MessageID");
this.OnMessageIDChanged();
}
}
}

And here's a little problem with Astoria - it doesn't work for me. I followed the Michael Sync's tutorial and made a few modifications, such as using of DataServiceQuery because WebDataQuery doesn't exist in final version of Astoria, etc. I ended up with two code snippets - the first is almost identical copy of the one in Michael Sync's article and the second one is using LINQ query instead of the CreateQuery method (I think both of these approaches lead to the same end). Here are the snippets:
SilverchatDBEntities entity =
new SilverchatDBEntities(new Uri("http://localhost:65373/WebDataService1.svc", UriKind.Absolute));
entity.MergeOption = MergeOption.OverwriteChanges;
DataServiceQuery<SCMessages> messages = entity.CreateQuery<SCMessages>("SCMessages");
messages.BeginExecute(
result =>
{
var mess = messages.EndExecute(result);
foreach (var mes in mess)
{
MessagesLB.Items.Add(mes.MessageAuthor);
}
},
null);
This doesn't do anything - it doesn't throw any exception and doesn't load any SCMessages neither. The second snippet is as follows:
SilverchatDBEntities entity =
new SilverchatDBEntities(new Uri("http://localhost:65373/WebDataService1.svc", UriKind.Absolute));
var query = (DataServiceQuery<SCMessages>) from m in entity.SCMessages select m;
query.BeginExecute(new AsyncCallback(result =>
{
try
{
var mes = query.EndExecute(result);
foreach (var r in mes)
{
MessagesLB.Items.Add(string.Format("{0}; {1} - {2}",
r.MessageDate.
ToString(
"d/M hh:mm",
CultureInfo.
InvariantCulture),
r.MessageAuthor,
r.MessageText));
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}), null);
This one throws exception at the 'foreach' loop - 'Object reference not set to an instance of an object'. I have no idea what the problem could be.

Chrasty,
I don't see any obvious errors yet. So in theory it shoulld work (I currently work almost daily with these kind of queries. A few questions:
1. can you use fiddler2 to see what is going over the wire. (If you don't know what fiddler is then google :-) and if after that you are using fiddler on localhost, then please add a '.' in the url in the browser (like http:\localhost.:1234\mywebsitehostingsilverlight.aspx), -> prevents an other google search)
2. Do you have a stack trace of the second one throwing an exception.
3. Did you try putting a breakpoint in your callback (first example) to see if it is called and with what result it is called
Hope these questions help a bit in solving the problem

Related

ASP.NET Web API Test instance initialization

I am writing some integration tests for my web API, which means that it has to be running during the execution of the tests. Is there any way to run it with an in-memory database instead of a real one based on SQL Server?
Also, I need to run a few instances at a time, so I need somehow to change the base address of each of them to be unique. For example, I could append to the base URL these instance IDs, that are mentioned in the code below.
Here is the code which I am using to run a new instance for my tests:
public static class WebApiHelper
{
private const string ExecutableFileExtension = "exe";
private static readonly Dictionary<Guid, Process> _instances = new();
public static void EnsureIsRunning(Assembly? assembly, Guid instanceId)
{
if (assembly is null)
throw new ArgumentNullException(nameof(assembly));
var executableFullName = Path.ChangeExtension(
assembly.Location, ExecutableFileExtension);
_instances.Add(instanceId, Process.Start(executableFullName));
}
public static void EnsureIsNotRunning(Guid instaceId)
=> _instances[instaceId].Kill();
}
Talking in general, is this a good way to create test instances, or maybe I am missing something? Asking this, because maybe there is another 'legal' way to achieve my goal.
Okay, so in the end, I came up with this super easy and obvious solution.
As was mentioned in the comments - using the in-memory database is not the best way to test, because relational features are not supported if using MS SQL.
So I decided to go another way.
Step 1: Overwrite the connection strings.
In my case, that was easy since I have a static IConfiguration instance and was need just to overwrite the connection string within that instance.
The method looks as follows:
private const string ConnectionStringsSectionName = "ConnectionStrings";
private const string TestConnectionStringFormat = "{0}_Test";
private static bool _connectionStringsOverwitten;
private static void OverwriteConnectionStrings()
{
if (_connectionStringsOverwitten)
return;
var connectionStrings = MyStaticConfigurationContainer.Configuration
.AsEnumerable()
.Where(entry => entry.Key.StartsWith(ConnectionStringsSectionName)
&& entry.Value is not null);
foreach (var connectionString in connectionStrings)
{
var builder = new SqlConnectionStringBuilder(connectionString.Value);
builder.InitialCatalog = string.Format(TestConnectionStringFormat,
builder.InitialCatalog);
MyStaticConfigurationContainer.Configuration[connectionString.Key] = builder.ConnectionString;
}
_connectionStringsOverwitten = true;
}
Of course, you would need to handle the database creation and deletion before and after running the tests, otherwise - your test DBs may become a mess.
Step 2: Simply run your web API instance within a separate thread.
In my case, I am using the NUnit test framework, which means I just need to implement the web API setup logic within the fixture. Basically, the process would be more or less the same for every testing framework.
The code looks as follows:
[SetUpFixture]
public class WebApiSetupFixture
{
private const string WebApiThreadName = "WebApi";
[OneTimeSetUp]
public void SetUp() => new Thread(RunWebApi)
{
Name = WebApiThreadName
}.Start();
private static void RunWebApi()
=> Program.Main(Array.Empty<string>());
// 'Program' - your main web app class with entry point.
}
Note: The code inside Program.Main(); will also look for connection strings in the MyStaticConfigurationContainer.Configuration which was changed in the previous step.
And that's it! Hope this could help somebody else :)

CRM 2011 - How to set data in records from subgrid in Entity using C# code

I am making a plugin in CRM 2011. The plugin is taking data from the Entity's subgrid by using fetchXML, making some calculate with the data and at the end of the plugin I want to set the new calculated data back in the subgrid, but I can't ...
I tried few ways to do that like:
(1)
private static OptionSetValue CreateOptionSet(int optionSetValue)
{
OptionSetValue optionSetInstance = new OptionSetValue();
optionSetInstance.Value = optionSetValue;
return optionSetInstance;
}
(2)
public void setVal(Entity entity, string attr, object val)
{
if (entity.Attributes.Contains(attr))
{
entity[attr] = val;
}
else
{
entity.Attributes.Add(attr, val);
}
}
and just
paid["zbg_paidamount"] = 400;
payment.Attributes["zbg_suggestedamount"] = paidVal;
But nothing works...
I am thinking maybe is from the type of the data that I am trying to set but not sure.
Please if you can help me I am desperate.
Thanks
Even though it looks like you've resolved your issue each section of your code has an issue with it...
(1) - Use the int Constructor for OptionSetValue:
(2) - don't worry about checking the value existing or not, just set it directly on the entity (also don't worry about accessing the Attributes collection)
payment["zbg_paidamount"] = new OptionSetValue(400);
In Response to Draiden's Comments
The indexer on the Entity class will automatically handle adding or updating a value. Here is an example LinqPad program:

Xilium.CefGlue how to execute JavaScript with return Value?

I'm quite new in programming multi-threading and I could not understand from the xelium example how I could execute a javascript and get the return value.
I have tested:
browser.GetMainFrame().ExecuteJavaScript("SetContent('my Text.')", null, 0);
the javascript is executed, but I this function don’t allow me to get the return value.
I should execute the following function to get all the text the user have written in the box..
browser.GetMainFrame().ExecuteJavaScript("getContent('')", null, 0);
the function TryEval should do this…
browser.GetMainFrame().V8Context.TryEval("GetDirtyFlag", out returninformation , out exx);
But this function can’t be called from the browser, I think it must be called from the renderer? How can I do so?
I couldn’t understand the explanations about CefRenderProcessHandler and OnProcessMessageReceived.. How to register a Scriptable Object and set my javascript & parameters?
Thx for any suggestions how I could solve this!
I have been struggling with this as well. I do not think there is a way to do this synchronously...or easily :)
Perhaps what can be done is this:
From browser do sendProcessMessage with all JS information to renderer
process. You can pass all kinds of parameters to this call in a structured way so encapsulating the JS method name and params in order should not be difficult to do.
In renderer process (RenderProcessHandler onProcessMessageReceived method) do TryEval on the V8Context and get the return value via out parameters and sendProcessMessage back to the
browser process with the JS return value (Note that this supports ordinary return semantics from your JS method).You get the browser instance reference in the onProcessMessageReceived so it is as easy as this (mixed pseudo code)
browser.GetMainFrame().CefV8Context.tryEval(js-code,out retValue, out exception);
process retValue;
browser.sendProcessMessage(...);
Browser will get a callback in the WebClient in onProcessMessageReceived.
There is nothing special here in terms of setting up JS. I have for example a loaded html page with a js function in it. It takes a param as input and returns a string. in js-code parameter to TryEval I simply provide this value:
"myJSFunctionName('here I am - input param')"
It is slightly convoluted but seems like a neat workable approach - better than doing ExecuteJavaScript and posting results via XHR on custom handler in my view.
I tried this and it does work quite well indeed....and is not bad as it is all non-blocking. The wiring in the browser process needs to be done to process the response properly.
This can be extended and built into a set of classes to abstract this out for all kinds of calls..
Take a look at the Xilium demo app. Most of the necessary wiring is already there for onProcessMessage - do a global search. Look for
DemoRendererProcessHandler.cs - renderer side this is where you will invoke tryEval
DemoApp.cs - this is browser side, look for sendProcessMessage - this will initiate your JS invocation process.
WebClient.cs - this is browser side. Here you receive messages from renderer with return value from your JS
Cheers.
I resolved this problem by returning the result value from my JavaScript function back to Xilium host application via an ajax call to a custom scheme handler. According to Xilium's author fddima it is the easiest way to do IPC.
You can find an example of how to implement a scheme handler in the Xilium's demo app.
Check out this post: https://groups.google.com/forum/#!topic/cefglue/CziVAo8Ojg4
using System;
using System.Windows.Forms;
using Xilium.CefGlue;
using Xilium.CefGlue.WindowsForms;
namespace CefGlue3
{
public partial class Form1 : Form
{
private CefWebBrowser browser;
public Form1()
{
InitializeCef();
InitializeComponent();
}
private static void InitializeCef()
{
CefRuntime.Load();
CefMainArgs cefArgs = new CefMainArgs(new[] {"--force-renderer-accessibility"});
CefApplication cefApp = new CefApplication();
CefRuntime.ExecuteProcess(cefArgs, cefApp);
CefSettings cefSettings = new CefSettings
{
SingleProcess = false,
MultiThreadedMessageLoop = true,
LogSeverity = CefLogSeverity.ErrorReport,
LogFile = "CefGlue.log",
};
CefRuntime.Initialize(cefArgs, cefSettings, cefApp);
}
private void Form1_Load(object sender, EventArgs e)
{
browser = new CefWebBrowser
{
Visible = true,
//StartUrl = "http://www.google.com",
Dock = DockStyle.Fill,
Parent = this
};
Controls.Add(browser);
browser.BrowserCreated += BrowserOnBrowserCreated;
}
private void BrowserOnBrowserCreated(object sender, EventArgs eventArgs)
{
browser.Browser.GetMainFrame().LoadUrl("http://www.google.com");
}
}
}
using Xilium.CefGlue;
namespace CefGlue3
{
internal sealed class CefApplication : CefApp
{
protected override CefRenderProcessHandler GetRenderProcessHandler()
{
return new RenderProcessHandler();
}
}
internal sealed class RenderProcessHandler : CefRenderProcessHandler
{
protected override void OnWebKitInitialized()
{
CefRuntime.RegisterExtension("testExtension", "var test;if (!test)test = {};(function() {test.myval = 'My Value!';})();", null);
base.OnWebKitInitialized();
}
}
}

Implementing Audit Log / Change History with MVC & Entity Framework

I am building in a Change History / Audit Log to my MVC app which is using the Entity Framework.
So specifically in the edit method public ActionResult Edit(ViewModel vm), we find the object we are trying to update, and then use TryUpdateModel(object) to transpose the values from the form on to the object that we are trying to update.
I want to log a change when any field of that object changes. So basically what I need is a copy of the object before it is edited and then compare it after the TryUpdateModel(object) has done its work. i.e.
[HttpPost]
public ActionResult Edit(ViewModel vm)
{
//Need to take the copy here
var object = EntityFramework.Object.Single(x=>x.ID = vm.ID);
if (ModelState.IsValid)
{
//Form the un edited view model
var uneditedVM = BuildViewModel(vm.ID); //this line seems to confuse the EntityFramework (BuildViewModel() is used to build the model when originally displaying the form)
//Compare with old view model
WriteChanges(uneditedVM, vm);
...
TryUpdateModel(object);
}
...
}
But the problem is when the code retrieves the "unedited vm", this is causing some unexpected changes in the EntityFramework - so that TryUpdateModel(object); throws an UpdateException.
So the question is - in this situation - how do I create a copy of the object outside of EntityFramework to compare for change/audit history, so that it does not affect or change the
EntityFramework at all
edit: Do not want to use triggers. Need to log the username who did it.
edit1: Using EFv4, not too sure how to go about overriding SaveChanges() but it may be an option
This route seems to be going nowhere, for such a simple requirement! I finally got it to override properly, but now I get an exception with that code:
public partial class Entities
{
public override int SaveChanges(SaveOptions options)
{
DetectChanges();
var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in modifiedEntities)
{
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry).GetModifiedProperties(); //This line throws exception The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'System.Data.Objects.EntityEntry'.
var currentValues = ObjectStateManager.GetObjectStateEntry(entry).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
//return base.SaveChanges();
return base.SaveChanges(options);
}
}
IF you are using EF 4 you can subscribe to the SavingChanges event.
Since Entities is a partial class you can add additional functionality in a separate file. So create a new file named Entities and there implement the partial method OnContextCreated to hook up the event
public partial class Entities
{
partial void OnContextCreated()
{
SavingChanges += OnSavingChanges;
}
void OnSavingChanges(object sender, EventArgs e)
{
var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in modifiedEntities)
{
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
}
}
If you are using EF 4.1 you can go through this article to extract changes
See FrameLog, an Entity Framework logging library that I wrote for this purpose. It is open-source, including for commercial use.
I know that you would rather just see a code snippet showing how to do this, but to properly handle all the cases for logging, including relationship changes and many-to-many changes, the code gets quite large. Hopefully the library will be a good fit for your needs, but if not you can freely adapt the code.
FrameLog can log changes to all scalar and navigation properties, and also allows you to specify a subset that you are interested in logging.
There is an article with high rating here at the codeproject: Implementing Audit Trail using Entity Framework . It seems to do what you want. I have started to use this solution in a project. I first wrote triggers in T-SQL in the database but it was too hard to maintain them with changes in the object model happening all the time.

Why is System.Data.Services.MimeTypeAttribute now only a class level attribute?

I'm getting started with Astoria/ADO.NET Data Services/WCF Data Services. Looking through a lot of the code samples out there, it appears that the MimeType attribute used to be a method level attribute. After installing the latest update, it is now a class level attribute.
If I have more than one Service Operation that I want to return as a certain MimeType, then it appears now that I have to create a new service for each operation. Is this correct?
Most examples are like this:
[WebGet]
[SingleResult]
[MimeType("application/pdf")]
public IQueryable<byte[]> FooPDF()
{
var result = from p in this.CurrentDataSource.MyPDFs
where p.FooID == 2
select p;
return result.Take(1).Select(p => p.PDF);
}
I get "Attribute 'MimeType' is not valid on this declaration type. It is only valid on 'class' declarations." when I compile, because now I can't do this.
Now, I have to do this:
[MimeType("FooPDF", "application/pdf")]
public class FooService : DataService<FooDBEntities>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetServiceOperationAccessRule("FooPDF", ServiceOperationRights.All);
}
[WebGet]
[SingleResult]
public IQueryable<byte[]> FooPDF()
{
var result = from p in this.CurrentDataSource.MyPDFs
where p.FooID == 2
select p;
return result.Take(1).Select(p => p.PDF);
}
}
What's worse is that I can't add duplicate MimeType attributes to my class.
Is all of this really by design, or am I missing something?
Thanks for reporting this bug to us. I have opened this at our end to track this issue
With the recent update, we added the support for blobs as a first class concept in the data services. If you have a blob association with an entity, then both server and client have ways to surface this. To know more about this, please refer to the following link: http://msdn.microsoft.com/en-us/library/ee473426(v=VS.100).aspx
Hope this helps.
Thanks
Pratik
[MSFT]

Categories

Resources