Change Address/Port of WSDL EndPointAddress at runtime? - wcf

So I currently have 2 WSDLs added as Service References in my solution. They look like this in my app.config file (I removed the "bindings" field, because it's uninteresting):
<system.serviceModel>
<client>
<endpoint address="http://localhost:8080/query-service/jse" binding="basicHttpBinding" bindingConfiguration="QueryBinding" contract="QueryService.Query" name="QueryPort" />
<endpoint address="http://localhost:8080/dataimport-service/jse" binding="basicHttpBinding" bindingConfiguration="DataImportBinding" contract="DataService.DataImport" name="DataImportPort" />
</client>
</system.serviceModel>
When I utilize a WSDL, it looks something like this:
using (DataService.DataClient dClient = new DataService.DataClient())
{
DataService.importTask impt = new DataService.importTask();
impt.String_1 = "someData";
DataService.importResponse imptr = dClient.importTask(impt);
}
In the "using" statement, when instantiating the DataClient object, I have 5 constructors available to me. In this scenario, I use the default constructor:
new DataService.DataClient()
which uses the built-in Endpoint Address string, which I assume is pulled from app.config. But I want the user of the application to have the option to change this value.
1) What's the best/easiest way of programatically obtaining this string?
2) Then, once I've allowed the user to edit and test the value, where should I store it?
I'd prefer having it be stored in a place (like app.config or equivalent) so that there is no need for checking whether the value exists or not and whether I should be using an alternate constructor. (Looking to keep my code tight, ya know?)
Any ideas? Suggestions?
EDIT
Maybe I should ask about these Alternate constructors as well.
For example, one of them looks like this:
new DataService.DataClient(string endPointConfigurationName,
string remoteAddress)
What values could get passed for "endPointConfigurationName" and "remoteAddress"?
EDIT2
Answering my own questions here, the "endPointConfigurationName" appears to be the same as the "name" in the app.config XML and the "remoteAddress" is formatted the same as "endpoint address" in the app.config XML.
Also! The answer to my first question about getting the EndPointAddresses is the following:
ClientSection clSection =
ConfigurationManager.GetSection("system.serviceModel/client") as ClientSection;
ChannelEndpointElementCollection endpointCollection =
clSection.ElementInformation.Properties[string.Empty].Value as ChannelEndpointElementCollection;
Dictionary<string, string> nameAddressDictionary =
new Dictionary<string, string>();
foreach (ChannelEndpointElement endpointElement in endpointCollection)
{
nameAddressDictionary.Add(endpointElement.Name,
endpointElement.Address.ToString());
}
EDIT3
Ok, I think I've figured out the 2nd half (and thus, full solution) to my problem. I found this on another website and I modified it to meet my needs:
Configuration configuration;
ServiceModelSectionGroup serviceModelSectionGroup;
ClientSection clientSection;
configuration =
ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
serviceModelSectionGroup =
ServiceModelSectionGroup.GetSectionGroup(configuration);
clientSection = serviceModelSectionGroup.Client;
foreach (ChannelEndpointElement endPt in clientSection.Endpoints)
{
MessageBox.Show(endPt.Name + " = " + endPt.Address);
}
configuration.Save();
With this code, we have access to the clientSection.Endpoints and can access and change all the member properties, like "Address". And then when we're done changing them, we can do configuration.Save() and all the values get written out to a user file.
Now here's the catch. In debug mode, the "configuration.save()" does not appear to actually persist your values from execution to execution, but when running the application normal (outside of debug mode), the values persist. (Which is good.) So that's the only caveat.
EDIT4
There is another caveat. The changes made to the WSDLs do not take effect during runtime. The application needs to be restarted to re-read the user config file values into memory (apparently.)
The only other thing that I might be interested in is finding a way (once the values have been changed) to revert the values to their defaults. Sure, you can probably delete the user file, but that deletes all of the custom settings.
Any ideas?
EDIT5
I'm thinking Dependency Injection might be perfect here, but need to research it more...
EDIT 6
I don't have comment privileges but you need to run
ConfigurationManager.RefreshSection("client");
to have the cache updated so that changes happen immediately.

If you're using Microsoft Add Web Reference to create your service reference, then I think you may have trouble changing the connection programmatically. Even if you did change the auto generated code, as soon as you did an Update Service Reference it'd be overwritten.
You're best bet is to scratch Microsoft's auto generated code and build your own WCF classes. It's not difficult, and offers lots of flexibility / scalability.
Here's an excellent article on this very subject.
As for storing the custom addresses, it would depend on your app whether it's a Silverlight, Windows or web app. My personal choice is the database.

Related

Programmatically modify web.config for a self-hosted service (not via changes to applicationHost.config)

I'm stuck on something I should probably move on from but it's driving me nuts...
I can programmatically update SSL certificate info for a self-hosted WCF service using:
Dim config As Microsoft.Web.Administration.Configuration = _
serverManager.GetApplicationHostConfiguration
site.Bindings.Clear()
Dim binding = site.Bindings.Add(ipport, cert.GetCertHash, store.Name)
serverManager.CommitChanges()
and can also change sections of my local web.config using a known file path starting with:
Dim cfg As System.Configuration.Configuration = _
System.Web.Configuration.WebConfigurationManager.OpenMappedWebConfiguration(ConfigFileMap, target)
but if I try to drill down to system.webServer/security/access using:
Dim accessSection = cfg.GetSection("system.webServer/security")
I get Nothing/null and further digging helpfully produces this status for the section "System.Configuration.IgnoreSection", which apparently indicates that System.Configuration doesn't want to play nice with that specific piece - even though it's not denied access in applicationHost as near as I can tell.
On the other hand, if I try to use Microsoft.Web.Administration I can only figure out how to make the change to applicationHost.config, not to the local web.config.
The only thing that seems to want to put the client certificate requirement (sslFlags setting) in the local web.config is IIS Manager (which also doesn't show the setting correctly if it is located in the applicationHost.config)
Obviously there are all sorts of ways to do this but I can't believe there isn't a simple dot net way (other than editing the xml). Does anyone know what the heck I am doing wrong?
Doh!!! Apparently I need to learn to read the manual (although MS doesn't make it easy).
You can simply do:
Using serverManager As New ServerManager
Dim config As Microsoft.Web.Administration.Configuration = _
serverManager.GetWebConfiguration("site name", "/application")
Dim accessSection As Microsoft.Web.Administration.ConfigurationSection = _
config.GetSection("system.webServer/security/access")
accessSection("sslFlags") = "Ssl,SslRequireCert"
serverManager.CommitChanges()
End Using
Retroactively obvious link: Relevant StackOverflow Question

How to build Query Parameter using Spring Traverson

I have a Spring Data Rest webservice with QueryDSL Web Support enabled so I can query any of the fields directly like below;
http://localhost:9000/api/prod1007?cinfo1=0126486035
And I was using Traverson to access this service but traverson is not generating the query parameter as above; below is my code (I have tried both withTemplateParameters() and withParameters() in Hop level)
Code:
Map<String,Object> parameters = new HashMap<String,Object>();
parameters.put("cinfo1", "0127498374");
PagedResources<Tbpinstance> items = traverson
.follow(Hop.rel("prod1007"))
.withTemplateParameters(parameters)
.toObject(resourceParameterizedTypeReference);
Any Help is much appreciated. Thanks!
Traverson needs to know where to put those parameters. They could be path parameters, or they could be query parameters. Furthermore, Traverson navigates the service from the root, so the parameters might need to be inserted somewhere in the middle, and not in the final step only.
For these reasons the server needs to clearly tell how to use the parameters. Traverson needs a HATEOAS-"directory" for the service. When Traverson HTTP GETs the http://localhost:9000/api document, it needs to contain a link similar to this:
"_links" : {
"product" : {
"href" : "http://localhost:9000/api/prod1007{?cinfo1}",
"templated" : true
},
}
Now it knows that the cinfo1 parameter is a query parameter and will be able to put it into its place.
#ZeroOne, you are entirely correct, that is what the response from the server should look like. Currently spring-hateoas does not support responses that look like that (I expect it will in the future as I have seen comments by Oliver Gierke indicating that spring-hateoas is going through a major upgrade).
As at the time of writing, to generate responses from the server as you describe, we have used spring-hateoas-ext mentioned in https://github.com/spring-projects/spring-hateoas/issues/169. You can find code at https://github.com/dschulten/hydra-java#affordancebuilder-for-rich-hyperlinks-from-v-0-2-0.
This is a 'drop in replacement' for spring-hateoas' ControllerLinkBuilder.
Here is the maven dependency we use (but check for the latest version).
<!-- Drop in replacement from spring-hateoas ControllerLinkBuilder -->
<dependency>
<groupId>de.escalon.hypermedia</groupId>
<artifactId>spring-hateoas-ext</artifactId>
<version>0.3.0-beta6</version>
</dependency>
Here's the import we use in our ResourceAssemblers.
import static de.escalon.hypermedia.spring.AffordanceBuilder.*;

Applying SSIS Package Configuration to multiple packages

I have about 85 SSIS packages that are using the same connection manager.
I understand that each package has its own connection manager.
I am trying to decide what would be the best configurations approach to simply set the connectionstring of the connection manager based on the server the packages are residing on.
I have visited all kinds of suggestions online, but cannot find anywhere the practice where I can simply copy the configuration from one package to the rest of the packages.
There are obviously many approaches such as XML file, SQL Server, Environment Variable, etc.
All the articles out there are pointing to use an Indirect method by using XML or SQL approach. Why would using an environment variable for just holding a connection string is such a bad approach?
Any suggestions are highly appreciated.
Thanks!
Why would using an environment variable for just holding a connection string is such a bad approach?
I find the environment variable or registry key configuration approach to be severely limited by the fact that it can only configure one item at a time. For a connection string, you'd need to define an environment variable for each catalog on a given server. Maybe it's only 2 or 3 and that's manageable. We had a good 30+ per database instance and we had multi-instanced machines so you can see how quickly this problem explodes into a maintenance nightmare. Contrast that with a table or xml based approach which can hold multiple configuration items for a given configuration key.
...best configurations approach to simply set the connectionstring of the connection manager based on the server the packages are residing on.
If you go this route, I'd propose creating a variable, ConnectionString and using it to configure the property. It's an extra step but again I find it's easier to debug a complex expression on a variable versus a complex expression on a property. With a variable, you can always pop a breakpoint on the package and look at the locals window to see the current value.
After creating a variable named ConnectionString, I right click on it, select Properties and set EvaluateAsExpression equal to True and the Expression property to something like "Data Source="+ #[System::MachineName] +"\\DEV2012;Initial Catalog=FOO;Provider=SQLNCLI11.1;Integrated Security=SSPI;"
When that is evaluated, it'd fill in the current machine's name (DEVSQLA) and I'd have a valid OLE DB connection string that connects to a named instance DEV2012.
Data Source=DEVSQLA\DEV2012;Initial Catalog=FOO;Provider=SQLNCLI11.1;Integrated Security=SSPI;
If you have more complex configuration needs than just the one variable, then I could see you using this to configure a connection manager to a sql table that holds the full repository of all the configuration keys and values.
...cannot find anywhere the practice where I can simply copy the configuration from one package to the rest of the packages
I'd go about modifying all 80something packages through a programmatic route. We received a passel of packages from a third party and they had not followed our procedures for configuration and logging. The code wasn't terribly hard and if you describe exactly the types of changes you'd make to solve your need, I'd be happy to toss some code onto this answer. It could be as simple as the following. After calling the function, it will modify a package by adding a sql server configuration on the SSISDB ole connection manager to a table called dbo.sysdtsconfig for a filter named Default.2008.Sales.
string currentPackage = #"C:\Src\Package1.dtsx"
public static void CleanUpPackages(string currentPackage)
{
p = new Package();
p.app.LoadPackage(currentPackage, null);
Configuration c = null;
// Apply configuration Default.2008.Sales
// ConfigurationString => "SSISDB";"[dbo].[sysdtsconfig]";"Default.2008.Sales"
// Name => MyConfiguration
c = p.Configurations.Add();
c.Name = "SalesConfiguration";
c.ConfigurationType = DTSConfigurationType.SqlServer;
c.ConfigurationString = #"""SSISDB"";""[dbo].[sysdtsconfig]"";""Default.2008.Sales""";
app.SaveToXml(sourcePackage, p, null);
}
Adding a variable in to the packages would not take much more code. Inside the cleanup proc, add code like this to add a new variable into your package that has an expression like the above.
string variableName = string.Empty;
bool readOnly = false;
string nameSpace = "User";
string variableValue = string.Empty;
string literalExpression = string.Empty;
variableName = "ConnectionString";
literalExpression = #"""Data Source=""+ #[System::MachineName] +""\\DEV2012;Initial Catalog=FOO;Provider=SQLNCLI11.1;Integrated Security=SSPI;""";
p.Variables.Add(variableName, readOnly, nameSpace, variableValue);
p.Variables[variableName].EvaluateAsExpression = true;
p.Variables[variableName].Expression = literalExpression;
Let me know if I missed anything or you'd like clarification on any points.

Consume WCF Data service in client application throws error

I am working on WCF Data service which imported stored procedure, as below.
[WebGet]
public List<GetMTSearchResultTest_Result> GettMTSearchResultTest()
{
MediaMarketResearch_PRODEntities ent = new MediaMarketResearch_PRODEntities();
return ent.GetMTSearchResultTest().ToList();
}
when i consuming this in my client application it says error as "The closed type MMRClient.MMRServiceReference.GetMTSearchResultTest_Result does not have a corresponding element settable property."
I am getting this error while bind to the grid view as below.
DataServiceContext context = new DataServiceContext(new Uri("http://localhost:4131/MMRDataService.svc/"));
IEnumerable<GetMTSearchResultTest_Result> empResult = context.Execute<GetMTSearchResultTest_Result>(new Uri("http://localhost:4131/MMRDataService.svc/GettMTSearchResultTest"));
GridView1.DataSource = empResult;
GridView1.DataBind();
Note: I imported this stored proc as complex type.
Please advice me on this.
Regards,
Jaydeep
I think this link may help you (see the selected answer).
Essentially, what the solution may be is to create a partial class for GetMTSearchResultTest_Result and decorate it with a DataServiceKey attribute, providing a non-nullable column that acts as a primary key (although I don't think it has to be unique).
So your partial class would look something like:
[DataServiceKey("YourKeyColumnName")]
public partial class GetMTSearchResultTest_Result {
}
If you're just doing reads, I don't think you'll need any implementation.
Hopefully this works. Let me know if there are issues/questions and I'll update accordingly.
You can always make a new service reference to a non data service. That is to a normal WCF service. You can simply have a [ContractOperation] returning a list of the troubled "complex types" and that's it.
This way you would have two services the original data service and a new normal WCF service. But this shouldn't be such an issue. You don't have to make the troubled "complex type" as a Entity.

Implementing LINQ-to-SQL transactions through WCF

I have a WCF service which is used to add tenders to the database, which is MS SQL Server 2005. WCF uses LINQ-to-SQL.
Each tender can have a lot of documents and a lot of items. The customers can add one object per service call. That is, the do something like this:
TendersServiceClient service = new TenderServiceClient();
service.BeginTransaction();
// Adding a new tender
service.AddTender(TenderDTO tenderInfo);
// Adding tender's documents
foreach (DocumentDTO documentInfo in documents)
service.AddTenderDocument(tenderInfo.TenderID, documentInfo);
// Adding tender's items
foreach (ItemDTO itemInfo in items)
service.AddTenderItem(tenderInfo.TenderID, itemInfo);
service.CommitTransaction();
Notice the BeginTransaction() and CommitTransaction(). That is, all the above procedure must either succeed completely or be rolled back completely. For example, if one of the items couldn't be inserted, then the whole tender shouldn't exist...
So the question is how do I implement this kind of transaction. The problem is that WCF is stateless, of course. So new DataContext is created for each service call. If I use a static DataContext instead, then I'll be able to use its built-in transactions capabilities, but then how can I handle other customers who can try to add another tender in the same time (they must be, of course, outside this transaction)?
So please help me with some kind of design pattern to achieve this. I am free to change the code both of the service and of the client, so feel free with your suggestions =)
Do you control the interface of the service?
If so, surely the elegant solution is for the service to accept an aggregate Tender object in a single method rather than having the chatty methods that you have now. The Tender would then have Items and Documents as subcollections, and the data access code could handle all the updates in single transaction much more easily.
Unless I am misunderstanding, it seems very similar to an Order/OrderDetails scenario, where the same logic applies.
First off, you would have to use transactional service calls here - and since you have a "initializing" call, a bunch of intermediary calls, and then possibly one to end all the calls, I would recommend you have a look at the "IsInitiating" and "IsTerminating" attributes on the OperationContract for the methods - this will allow you to specify one method to start your session, and one that will finish it.
Next, make sure to configure your service as a transactional service by putting the "TransactionFlow" attribute on either the service or all operations - whichever you prefer.
In your client code, you'll have to use System.Transactions to create a TransactionScope, which will wrap your service calls. This is a lightweight or a fully two-phased distributed transaction coordinator - depending on what your calls do in detail.
Something along those lines:
1) Mark your binding as transactional:
<bindings>
<wsHttpBinding>
<binding name="TransactionalWsHttp" transactionFlow="true" />
</wsHttpBinding>
</bindings>
2) Service contract:
[ServiceContract]
public interface ITenderService
{
// method to start your submission process
[OperationContract(IsInitiating=true, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void StartTenderProcess();
// all your other methods "in between"
[OperationContract(IsInitiating=false, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void AddTender()
[OperationContract(IsInitiating=false, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void AddTenderDocument()
[OperationContract(IsInitiating=false, IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void AddTenderItem()
...
// method to end your submission process
[OperationContract(IsInitiating=false, IsTerminating=true)]
[TransactionFlow(TransactionFlowOption.Mandatory]
public void FinishTenderProcess();
}
3) In your client code:
using (TransactionScope ts = new TransactionScope())
{
serviceClient.StartTenderProcess();
.....
serviceClient.FinishTenderProcess();
ts.Complete(); // Transaction Commit
}
Does that help you for the time being to get you started ??
Marc