Configuring NServicebus 4.7 with a predefined Autofac container - autofac

I'm a bit at a loss here. I'm developing a hosted NServicebus (v4.7) windows service and using Autofac (v3.5) to do my own type resolution. Given the NServicebus documentation I thought it was possible to add my own container in NServicebus's endpoint configuration. However no matter what I do, I can't get it to work. My current endpointconfig is the following:
using System;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Reflection;
using Autofac;
using HyFlo.Base;
using NServiceBus;
using NServiceBus.ObjectBuilder.Autofac;
using NServiceBus.ObjectBuilder.Common.Config;
namespace HyFlo.Service
{
/// <summary>
/// EndpointConfig
/// </summary>
public class EndpointConfig : IConfigureThisEndpoint, INeedInitialization, AsA_Publisher
{
public void Init()
{
TraceWriter.Info("Scannning for Hyflo assemblies .. ");
var hyfloAssemblies = Directory.GetFiles(Directory.GetCurrentDirectory(), "Hyflo.*.dll",
SearchOption.TopDirectoryOnly);
TraceWriter.Info("Initializing Autofac with assemblies .. ");
try
{
var builder = new ContainerBuilder();
Assembly[] allAssemblies = hyfloAssemblies.Select(Assembly.LoadFile).ToArray();
builder.RegisterAssemblyTypes(allAssemblies).AsImplementedInterfaces().InstancePerLifetimeScope();
IContainer container = builder.Build();
TraceWriter.Trace(
String.Format(
"Found {0} assembl{1}. Configuring NServicebus to use Autofac preinitialized with these assembl{1} ..",
allAssemblies.Count(), allAssemblies.Count() == 1 ? "y" : "ies"));
Configure.With().UsingContainer(new AutofacObjectBuilder(container));
}
catch (Exception ex)
{
TraceWriter.Trace(String.Format("Exception occurred during initialization. Exception is: {0}\n{1}",
ex.Message, ex.StackTrace));
}
TraceWriter.Info("Initializing database en msmqsubscription .. ");
string databusBasePath = ConfigurationManager.AppSettings["DataBusBasePath"] ?? "";
TraceWriter.Trace(String.Format("Setting Databus's basepath to '{0}' ..", databusBasePath));
Configure.With().FileShareDataBus(databusBasePath);
Configure.With().MsmqSubscriptionStorage();
}
}
}
This code itself works however when the NServicebus initialisation continues an exception pops up:
Failed to execute installers: System.InvalidOperationException: Cannot configure properties for a type that hasn't been configured yet: NServiceBus.Un
icast.UnicastBus
at NServiceBus.ObjectBuilder.Autofac.AutofacObjectBuilder.ConfigureProperty(Type component, String property, Object value) in c:\BuildAgent\work\1b
05a2fea6e4cd32\src\NServiceBus.Core\ObjectBuilder\Autofac\AutofacObjectBuilder.cs:line 0
at NServiceBus.ObjectBuilder.Common.CommonObjectBuilder.ConfigureProperty[T](String propertyName, Object value) in c:\BuildAgent\work\1b05a2fea6e4c
d32\src\NServiceBus.Core\ObjectBuilder\Common\CommonObjectBuilder.cs:line 110
at NServiceBus.Unicast.Config.FinalizeUnicastBusConfiguration.RegisterMessageOwnersAndBusAddress(IEnumerable'1 knownMessages) in c:\BuildAgent\work
\1b05a2fea6e4cd32\src\NServiceBus.Core\Unicast\Config\FinalizeUnicastBusConfiguration.cs:line 57
at NServiceBus.Unicast.Config.FinalizeUnicastBusConfiguration.FinalizeConfiguration() in c:\BuildAgent\work\1b05a2fea6e4cd32\src\NServiceBus.Core\U
nicast\Config\FinalizeUnicastBusConfiguration.cs:line 24
at NServiceBus.Configure.<>c__DisplayClass23`1.b__20(Type t) in c:\BuildAgent\work\1b05a2fea6e4cd32\src\NServiceBus.Core\Configu
re.cs:line 555
at System.Collections.Generic.List'1.ForEach(Action'1 action)
at NServiceBus.Configure.ActivateAndInvoke[T](Action'1 action, Nullable'1 thresholdForWarning) in c:\BuildAgent\work\1b05a2fea6e4cd32\src\NServiceB
us.Core\Configure.cs:line 561
at NServiceBus.Configure.Initialize() in c:\BuildAgent\work\1b05a2fea6e4cd32\src\NServiceBus.Core\Configure.cs:line 361
at NServiceBus.Hosting.Windows.Installers.WindowsInstaller.RunInstall() in c:\BuildAgent\work\1b05a2fea6e4cd32\src\NServiceBus.Hosting.Windows\Inst
allers\WindowsInstaller.cs:line 38
No matter what I do, for some reason NServicebus's initialisation can't continue. It looks like my own Autofac container isn't properly configured in NServicebus by using the Configure.With().UsingContainer() method but I've tried all kind of options but every time it boils down to the same exception that is being thrown. Any ideas what I'm doing wrong here?

I found the answer to the problem. I scanned pretty much all DLL's to be included in Nservicebus's Autofac container. This also includes the DLL that contains the endpointconfig. Now when I exclude the DLL containing the endpoint, all goes well. I've now isolated pretty much all classes from the endpointconfig-DLL except for the message handlers. And now all goes well!

Try changing the code like this:
Configure configure = Configure.With();
// rest of container initialization
configure.AutofacBuilder(container);
// other stuff
configure.FileShareDataBus(databusBasePath);
configure.MsmqSubscriptionStorage();
and see if it works.

Related

Implement partial method to login to Soap Service

I have problems logging in to old style Soap Service using NET 6.0. If I use 4.8 the login process is straight forward. But the generetad code from http://media.atg.se:80/info/PartnerInfoService/version11?WSDL differs between 6.0 and 4.8. In 6.0 I get this "todo" from generated code:
public partial class PartnerInfoServicePortClient : System.ServiceModel.ClientBase<ATGAIS.PartnerInfoServicePort>, ATGAIS.PartnerInfoServicePort
{
/// <summary>
/// Implement this partial method to configure the service endpoint.
/// </summary>
/// <param name="serviceEndpoint">The endpoint to configure</param>
/// <param name="clientCredentials">The client credentials</param>
public static partial void ConfigureEndpoint(string serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
.
.
.
//more generated methods
.
.
.
}
I just can't figure out HOW to implement it. The site requires ClientCredentials which I don't know how to set in 6.0. For 4.8 it was very simple as follows:
var partnerInfoService = new PartnerInfoService();
//Log in with credentials
try
{
partnerInfoService.Credentials = new NetworkCredential("Username", "Password");
}
catch (Exception ex)
{
MessageBox.Show($#"Error: {ex.Message}");
throw;
}
As it says you should implement the ConfigureEndpoint method, but for that you should create a new class which inherits from PartnerInfoServicePortClient, and... this is so boring...
So the easy way is convert this partial method into a real method, and add the code of the method according to your needs. What I mean is replacing this line:
public static partial void ConfigureEndpoint(string serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
whith this line:
static public void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials)
{
}
As an example, I use this code to modify the configuration:
static public void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials)
{
var httpBinding = serviceEndpoint.Binding as BasicHttpBinding;
httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
httpBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
}
This code setup the service client to use Kerberos (Windows domain\username) authentication, but you can adjust the code to use any other authentication method.
PD: I am using Visual Studio 2022 and Net Core 6

EF Core Migrations in Azure Function startup

According to https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection the service provider should not be used until AFTER the startup has completed running. Indeed, if I try to get a registered service it will fail.
Example:
[assembly: FunctionsStartup(typeof(Startup))]
namespace Fx {
public sealed class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) {
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddEnvironmentVariables();
var configuration = configurationBuilder.Build();
builder.Services.AddInfrastructure(configuration);
builder.Services.AddApplication();
var serviceProvider = builder.Services.BuildServiceProvider();
DependencyInjection.AddDatabase(serviceProvider).GetAwaiter().GetResult();
}
}
}
public static class DependencyInjection {
public static async Task AddDatabase(IServiceProvider services) {
using var scope = services.CreateScope();
var serviceProvider = scope.ServiceProvider;
var context = serviceProvider.GetRequiredService<ApplicationDbContext>();
//Error generated here
if (context.Database.IsSqlServer()) {
await context.Database.MigrateAsync();
}
await ApplicationDbContextSeed.SeedSamplePersonnelDataAsync(context);
}
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration) {
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddScoped<IApplicationDbContext>(provider => provider.GetService<ApplicationDbContext>());
return services;
}
}
This produces the following error
Microsoft.EntityFrameworkCore: No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
Is there a good option for migrating and seeding during startup?
The easiest way I found to run code after startup was by registering a custom IWebJobsStartup by using the WebJobsStartupAttribute (the FunctionsStartupAttribute actually also inherits from this attribute). In the WebJobsStartup class you'll need to register your extension using the AddExtension where you are able to use dependency injection and seed your database. My code:
[assembly: WebJobsStartup(typeof(DbInitializationService), "DbSeeder")]
namespace Our.Database.Seeder
{
public class DbInitializationService : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.AddExtension<DbSeedConfigProvider>();
}
}
[Extension("DbSeed")]
internal class DbSeedConfigProvider : IExtensionConfigProvider
{
private readonly IServiceScopeFactory _scopeFactory;
public DbSeedConfigProvider(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void Initialize(ExtensionConfigContext context)
{
using var scope = _scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetService<YourDbContext>();
dbContext.Database.EnsureCreated();
// Further DB seeding, etc.
}
}
}
According to your code, I assume that you're building something aligned to the CleanArchitecture Repository on Github. https://github.com/jasontaylordev/CleanArchitecture
The main difference between this repo and your apporach, is that you're obviously not using ASP.NET, which is not a problem at all, but requires a little bit more configuration work.
The article already mentioned (https://markheath.net/post/ef-core-di-azure-functions) refers another blogpost (https://dev.to/azure/using-entity-framework-with-azure-functions-50aa), which briefly explains that EntityFramework Migrations are not capable of auto-discovering your migrations in an Azure Function. Therefore, you need to implement an instance of IDesignTimeDbContextFactory. I also stumbled upon it in the microsoft docs:
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory
You could for example place it inside your Infrastructure\Persistence\Configurations folder. (Once again, I'm only assuming that you're following the CleanArchitecture repo structure)
DI in AZURE Functions
Caveats
A series of registration steps run before and after the runtime processes the startup class. Therefore, keep in mind the following items:
The startup class is meant for only setup and registration. Avoid using services registered at startup during the startup process. For instance, don't try to log a message in a logger that is being registered during startup. This point of the registration process is too early for your services to be available for use. After the Configure method is run, the Functions runtime continues to register additional dependencies, which can affect how your services operate.
The dependency injection container only holds explicitly registered types. The only services available as injectable types are what are setup in the Configure method. As a result, Functions-specific types like BindingContext and ExecutionContext aren't available during setup or as injectable types

Add a self hosted SignalR server to a .Net Core Worker Service

I'm trying to extend a .NET Core Worker Service (<Project Sdk="Microsoft.NET.Sdk.Worker">) with SignalR (self hosted web app).
All the examples/tutorials/docs I have found are based on web applications, so they don't fit my case.
This is what I've done until now:
MyService Program.cs:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
DependencyBuilder.Build(hostContext, services); // inject the stuff I need in my service
// create a SignalR Web host
SignalRWebHostCreator.CreateHost(services, "http://localhost:8090", (endpoints) => {
endpoints.MapHub<MyHub>("/result");
});
});
}
and the class I want to use to "extend" the servie with a SignalR server application.
public class SignalRWebHostCreator
{
public static void CreateHost(IServiceCollection services, string serviceUrl, Action<IEndpointRouteBuilder> mapHubs)
{
services.AddSignalR(); // is it ok here ?
WebHost.CreateDefaultBuilder()
.UseUrls(serviceUrl)
.Configure((IApplicationBuilder app) => {
app.UseRouting();
app.Map("/check", config => { // just a test: it works!
config.Run(async context =>
{
context.Response.ContentType = "text/plain";
byte[] data = System.Text.Encoding.UTF8.GetBytes("OK");
await context.Response.Body.WriteAsync(data, 0, data.Length);
await context.Response.Body.FlushAsync();
});
});
app.UseEndpoints(endpoints =>
{
//endpoints.MapHub<ClockHub>("/hubs/clock"); // ERROR
//endpoints.MapHub<PingHub>("/ping"); // ERROR
//mapHubs(endpoints); // ERROR
});
})
.Build().Run();
}
}
(ClockHub is taken from MS example and PingHub is another simple Hub I tried to use instead of my "injected" Hubs)
It starts the web application properly and it responds properly to the url http://localhost:8090/check.
When I uncomment the calls to enpoint.MapHub() or my cusom Actions I have this error:
System.InvalidOperationException: 'Unable to find the required services. Please add all the required services by calling 'IServiceCollection.AddSignalR' inside the call to 'ConfigureServices(...)' in the application startup code.'
2nd try:
Seems like service.AddSignalR() is not doing its job, so I added this in SignalRWebHostCreator:
.Configure((IApplicationBuilder app) => {
app.ApplicationServices = services.BuildServiceProvider();
and now I have this error:
System.InvalidOperationException: 'Unable to resolve service for type 'System.Diagnostics.DiagnosticListener' while attempting to activate 'Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware'.'
that at least has a callstack:
_This exception was originally thrown at this call stack:
Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(System.IServiceProvider)
Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(System.IServiceProvider, System.Type, object[])
Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddleware.AnonymousMethod__0(Microsoft.AspNetCore.Http.RequestDelegate)
Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
Microsoft.AspNetCore.Hosting.WebHost.BuildApplication()
Microsoft.AspNetCore.Hosting.WebHost.StartAsync(System.Threading.CancellationToken)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.GetResult()_
If I add services.AddSingleton(new System.Diagnostics.DiagnosticListener("diagnostic listener")); I can use endpoints.MapHub(..) without errors but now a call to http://8090/check returns a 500 internal error, so I don't think this is the right way to solve the issue.
I found some example using WebApp from Microsoft.Owin.hosting.
It requires Microsoft.Owin.4.1.0, Microsoft.Owin.Hosting.4.1.0 and Owin.1.0.0 and the last one require Net Framework 4.6.1, I don't want this.
I have included Microsoft.AspNetCore.Owin.3.1.2 (100% .NET Core) but that does not offer WebApp or something similar.
I started experiencing same error when I upgraded nuget EFCore package to new version.
I noticed, that in my bin directory, a System.Diagnostics.DiagnosticSource.dll appeared, while when I downgraded - it disappeared.
I suspect, that DiagnosticListener type from old assembly version is registered in DI container, while on activation newer version is expected. Or reverse - I didn't dig that deep.
My solution was to revert EFCore to match Product version of System.Diagnostics.DiagnosticSource.dll so it will not appear in bin folder.

Asp.Net Web API Error: The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'

Simplest example of this, I get a collection and try to output it via Web API:
// GET api/items
public IEnumerable<Item> Get()
{
return MyContext.Items.ToList();
}
And I get the error:
Object of type
'System.Data.Objects.ObjectQuery`1[Dcip.Ams.BO.EquipmentWarranty]'
cannot be converted to type
'System.Data.Entity.DbSet`1[Dcip.Ams.BO.EquipmentWarranty]'
This is a pretty common error to do with the new proxies, and I know that I can fix it by setting:
MyContext.Configuration.ProxyCreationEnabled = false;
But that defeats the purpose of a lot of what I am trying to do. Is there a better way?
I would suggest Disable Proxy Creation only in the place where you don't need or is causing you trouble. You don't have to disable it globally you can just disable the current DB context via code...
[HttpGet]
[WithDbContextApi]
public HttpResponseMessage Get(int take = 10, int skip = 0)
{
CurrentDbContext.Configuration.ProxyCreationEnabled = false;
var lista = CurrentDbContext.PaymentTypes
.OrderByDescending(x => x.Id)
.Skip(skip)
.Take(take)
.ToList();
var count = CurrentDbContext.PaymentTypes.Count();
return Request.CreateResponse(HttpStatusCode.OK, new { PaymentTypes = lista, TotalCount = count });
}
Here I only disabled the ProxyCreation in this method, because for every request there is a new DBContext created and therefore I only disabled the ProxyCreation for this case .
Hope it helps
if you have navigation properties and you do not want make them non virtual, you should using JSON.NET and change configuration in App_Start to using JSON not XML!
after install JSON.NET From NuGet, insert this code in WebApiConfig.cs in Register method
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
If you have navigation properties make them non virtual. Mapping will still work but it prevents the creation of Dynamic Proxy entities which cannot be serialized.]
Not having lazy loading is fine in a WebApi as you don't have a persistent connection and you ran a .ToList() anyway.
I just disabled proxy classes on a per needed basis:
// GET: ALL Employee
public IEnumerable<DimEmployee> Get()
{
using (AdventureWorks_MBDEV_DW2008Entities entities = new AdventureWorks_MBDEV_DW2008Entities())
{
entities.Configuration.ProxyCreationEnabled = false;
return entities.DimEmployees.ToList();
}
}
Add the following code in Application_Start function of Global.asax.cs:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters
.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
This instruct the API to serialize every response into JSON and remove XML responses.
In my case the object being returned had a property within it with a type that did not have an argumentless/default constructor. By adding a zero-argument constructor to that type the object could be serialized successfully.
I had the same problem and my DTO was missing an parameter less constructor.
public UserVM() { }
public UserVM(User U)
{
LoginId = U.LoginId;
GroupName = U.GroupName;
}
First constructor was missing.
I got this error message and it turns out the problem was that I had accidentally set my class to use the same serialized property name for two properties:
public class ResultDto
{
//...
[JsonProperty(PropertyName="DataCheckedBy")]
public string ActualAssociations { get; set; }
[JsonProperty(PropertyName="DataCheckedBy")]
public string ExpectedAssociations { get; set; }
//...
}
If you're getting this error and you aren't sending entities directly through your API, copy the class that's failing to serialize to LINQPad and just call JsonConvert.SerializeObject() on it and it should give you a better error message than this crap. As soon as I tried this it gave me the following error message: A member with the name 'DataCheckedBy' already exists on 'UserQuery+ResultDto'. Use the JsonPropertyAttribute to specify another name.
After disable Proxy Creation, use eager loading (Include()) to load the proxy object.
In my Project EntityCollection returned from the WebApi action method.
Configuration.ProxyCreationEnabled = false not applicable. I have tried the below approach it is working fine for me.
Control Panel.
2.Turn on Windows Features on or off
Choose Internet Information Service
Check all the World Wide Web Components it would be better to check all the components in IIS.
Install the components.
Go to (IIS) type inetmgr in command prompt.
select the published code in the Virtual directory.
Convert into application
Browse it the application.
The answer by #Mahdi perfectly fixes the issue for me, however what I noticed is that if my Newtonsoft.JSON is 11.0 version then it doesn't fix the issue, but the moment I update Newtonsoft.JSON to latest 13.0 it starts working.

How to consume a complex object from a sproc using WCF Data Services / OData?

Using WCF Data Services (and the latest Entity Framework), I want to return data from a stored procedure. The returned sproc fields do not match 1:1 any entity in my db, so I create a new complex type for it in the edmx model (rather than attaching an existing entity):
Right-click the *.edmx model / Add / Function Import
Select the sproc (returns three fields) - GetData
Click Get Column Information
Add the Function Import Name: GetData
Click Create new Complex Type - GetData_Result
In the service, I define:
[WebGet]
public List<GetData_Result> GetDataSproc()
{
PrimaryDBContext context = new PrimaryDBContext();
return context.GetData().ToList();
}
I created a quick console app to test, and added a reference to System.Data.Services and System.Data.Services.Client - this after running Install-Package EntityFramework -Pre, but the versions on the libraries are 4.0 and not 5.x.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Services.Client;
using ConsoleApplication1.PrimaryDBService;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
DataServiceContext context = new DataServiceContext(new Uri("http://localhost:50100/PrimaryDataService1.svc/"));
IEnumerable<GetData_Result> result = context.Execute<GetData_Result>(new Uri("http://localhost:50100/PrimaryDataService1.svc/GetDataSproc"));
foreach (GetData_Result w in result)
{
Console.WriteLine(w.ID + "\t" + w.WHO_TYPE_NAME + "\t" + w.CREATED_DATE);
}
Console.Read();
}
}
}
I didn't use the UriKind.Relative or anything else to complicate this.
When I navigate in the browser to the URL, I see data, but when I consume it in my console app, I get nothing at all.
Adding tracing to the mix:
<system.diagnostics>
<sources>
<source name="System.ServiceModel" switchValue="Information, ActivityTracing" propagateActivity="true">
<listeners>
<add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="c:\temp\WebWCFDataService.svclog" />
</listeners>
</source>
</sources>
</system.diagnostics>
... and opening using the Microsoft Service Trace Viewer, I see two idential warnings:
Configuration evaluation context not found.
<E2ETraceEvent xmlns="http://schemas.microsoft.com/2004/06/E2ETraceEvent">
<System xmlns="http://schemas.microsoft.com/2004/06/windows/eventlog/system">
<EventID>524312</EventID>
<Type>3</Type>
<SubType Name="Warning">0</SubType>
<Level>4</Level>
<TimeCreated SystemTime="2012-04-03T14:50:11.8355955Z" />
<Source Name="System.ServiceModel" />
<Correlation ActivityID="{66f1a241-2613-43dd-be0c-341149e37d30}" />
<Execution ProcessName="WebDev.WebServer40" ProcessID="5176" ThreadID="10" />
<Channel />
<Computer>MyComputer</Computer>
</System>
<ApplicationData>
<TraceData>
<DataItem>
<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Warning">
<TraceIdentifier>http://msdn.microsoft.com/en-US/library/System.ServiceModel.EvaluationContextNotFound.aspx</TraceIdentifier>
<Description>Configuration evaluation context not found.</Description>
<AppDomain>fd28c9cc-1-129779382115645955</AppDomain>
</TraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
</E2ETraceEvent>
So why am I able to see data from the browser, but not when consumed in my app?
-- UPDATE --
I downloaded the Microsoft WCF Data Services October 2011 CTP which exposed DataServiceProtocolVersion.V3, created a new host and client and referenced Microsoft.Data.Services.Client (v4.99.2.0). Now getting the following error on the client when trying iterate in the foreach loop:
There is a type mismatch between the client and the service. Type
'ConsoleApplication1.WcfDataServiceOctCTP1.GetDataSproc_Result' is an
entity type, but the type in the response payload does not represent
an entity type. Please ensure that types defined on the client match
the data model of the service, or update the service reference on the
client.
I tried the same thing by referencing the actual entity - works fine, so same issue.
Recap: I want to create a high-performing WCF service DAL (data access layer) that returns strongly-typed stored procedures. I initially used a "WCF Data Services" project to accomplish this. It seems as though it has its limitations, and after reviewing performance metrics of different ORM's, I ended up using Dapper for the data access inside a basic WCF Service.
I first created the *.edmx model and created the POCO for my sproc.
Next, I created a base BaseRepository and MiscDataRepository:
namespace WcfDataService.Repositories
{
public abstract class BaseRepository
{
protected static void SetIdentity<T>(IDbConnection connection, Action<T> setId)
{
dynamic identity = connection.Query("SELECT ##IDENTITY AS Id").Single();
T newId = (T)identity.Id;
setId(newId);
}
protected static IDbConnection OpenConnection()
{
IDbConnection connection = new SqlConnection(WebConfigurationManager.ConnectionStrings["PrimaryDBConnectionString"].ConnectionString);
connection.Open();
return connection;
}
}
}
namespace WcfDataService.Repositories
{
public class MiscDataRepository : BaseRepository
{
public IEnumerable<GetData_Result> SelectAllData()
{
using (IDbConnection connection = OpenConnection())
{
var theData = connection.Query<GetData_Result>("sprocs_GetData",
commandType: CommandType.StoredProcedure);
return theData;
}
}
}
}
The service class:
namespace WcfDataService
{
public class Service1 : IService1
{
private MiscDataRepository miscDataRepository;
public Service1()
: this(new MiscDataRepository())
{
}
public Service1(MiscDataRepository miscDataRepository)
{
this.miscDataRepository = miscDataRepository;
}
public IEnumerable<GetData_Result> GetData()
{
return miscDataRepository.SelectAllData();
}
}
}
... and then created a simple console application to display the data:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Service1Client client = new Service1Client();
IEnumerable<GetData_Result> result = client.GetData();
foreach (GetData_Result d in result)
{
Console.WriteLine(d.ID + "\t" + d.WHO_TYPE_NAME + "\t" + d.CREATED_DATE);
}
Console.Read();
}
}
}
I also accomplished this using PetaPOCO, which took much less time to setup than Dapper - a few lines of code:
namespace PetaPocoWcfDataService
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
public class Service1 : IService1
{
public IEnumerable<GetData_Result> GetData()
{
var databaseContext = new PetaPoco.Database("PrimaryDBContext"); // using PetaPOCO for data access
databaseContext.EnableAutoSelect = false; // use the sproc to create the select statement
return databaseContext.Query<GetData_Result>("exec sproc_GetData");
}
}
}
I like how quick and simple it was to setup PetaPOCO, but using the repository pattern with Dapper will scale much better for an enterprise project.
It was also quite simple to create complex objects directly from the EDMX - for any stored procedure, then consume them.
For example, I created complex type return type called ProfileDetailsByID_Result based on the sq_mobile_profile_get_by_id sproc.
public ProfileDetailsByID_Result GetAllProfileDetailsByID(int profileID)
{
using (IDbConnection connection = OpenConnection("DatabaseConnectionString"))
{
try
{
var profile = connection.Query<ProfileDetailsByID_Result>("sq_mobile_profile_get_by_id",
new { profileid = profileID },
commandType: CommandType.StoredProcedure).FirstOrDefault();
return profile;
}
catch (Exception ex)
{
ErrorLogging.Instance.Fatal(ex); // use singleton for logging
return null;
}
}
}
So using Dapper along with some EDMX entities seems to be a nice quick way to get things going. I may be mistaken, but I'm not sure why Microsoft didn't think this all the way through - no support for complex types with OData.
--- UPDATE ---
So I finally got a response from Microsoft, when I raised the issue over a month ago:
We have done research on this and we have found that the Odata client
library doesn’t support complex types. Therefore, I regret to inform
you that there is not much that we can do to solve it.
*Optional: In order to obtain a solution for this issue, you have to use a Xml to Linq kind of approach to get the complex types.
Thank you very much for your understanding in this matter. Please let
me know if you have any questions. If we can be of any further
assistance, please let us know.
Best regards,
Seems odd.