1

I need to change how my C# application connects to a server to send a request and get a response. I'm not great with C# so I apologize if this explanation is confusing. Currently the program appears to use some magic that uses static information from the app.config file:

<configuration>
  <system.serviceModel>
    <client>
      <!-- important information here -->
    </client>
  </system.serviceModel>
</configuration>

I tried changing the values inside of the <client> element, but this only reads changes on program restart.

I would like to connect in a different way. Here is what I envision: I can access the data in the app.config file, store that in a few variables, and allow the user to change the values as necessary. They hit the "connect" button when they've tweaked the values to work for them, and THEN I make the connection with this code. I tried to find an answer to this but I couldn't find any tutorials on how to do this, so any links are appreciated too.

(this question is spawned from the answers to this question)

All help is appreciated and thanks in advance!

EDIT:

I have Hoerster's solution mostly working, but I got a few errors with his, so I changed the following lines:

Uri calcService = new Uri("http://localhost:8000/ServiceModelSamples/Service/CalculatorService");

(using direct URI because I didn't want to mess with app.config)

CalculatorClient.CalculatorClient calcClient = new CalculatorClient.CalculatorClient(calcBinding, calcEndpoint);

I had my service reference named CalculatorClient, so I had to instantiate a CalculatorClient from that reference, thus the double "CalculatorClient".

When I run my client (with the service running) I get the following exception in my client:

An unhandled exception of type 'System.ServiceModel.ProtocolException' occurred in mscorlib.dll

Additional information: Content Type text/xml; charset=utf-8 was not supported by service http://localhost:8000/ServiceModelSamples/Service/CalculatorService.  The client and service bindings may be mismatched.

I'm a bit confused by this because I didn't touch any of the Reference.cs code. It hovers over the following line in my Reference.cs:

return base.Channel.Add(n1, n2);

I feel like I'm really close to getting this figured out, but that I'm just missing one thing...

Community
  • 1
  • 1
adam_0
  • 6,920
  • 6
  • 40
  • 52
  • I changed the binding on the server/service to BasicHttpBinding - that may be why you're getting the protocol exception. I'll update my answer with the service configuration. – David Hoerster Aug 04 '10 at 16:59

1 Answers1

2

Well, sounds like there's two questions here:

  1. How to get app.config to update during runtime (which is possible, but isn't automatic); and,
  2. How to dynamically set bindings and endpoints at runtime based upon app.config settings.

For updating your app.config at runtime, call RefreshSection. So you would probably want to hook up that call to the client button click event.

ConfigurationManager.RefreshSection("appSettings");
Console.WriteLine(ConfigurationManager.AppSettings["foo"]);

For dynamically setting your binding and endpoint information at runtime, that's definitely possible. I put a quick example together based on the MSDN Calculator Service examples. (I used BasicHttpBinding instead of WSHttpBinding just to keep it simple.) UPDATE Here's the only change I made to the example service:

// Step 3 of the hosting procedure: Add a service endpoint.
selfHost.AddServiceEndpoint(
    typeof(ICalculator),
    new BasicHttpBinding(),
    "CalculatorService");

The steps I took were:

  1. Create the service from the MSDN code sample and run it (so that the service endpoint was listening);
  2. Created a basic C# console application and added a service reference to my service so that it would create the client reference classes in my client console app. (It also created an app.config, but I'm not using any of those app.config settings it created.);
  3. Wrote the following code which sets up the Binding and EndpointAddress that my auto-generated client code will use to connect to the service:

*

static void Main(string[] args) {
    //refresh the appSettings section
    ConfigurationManager.RefreshSection("appSettings");

    //this could come from app.configs appSettings (value = "http://localhost:8000/ServiceModelSamples/Service/CalculatorService")
    Uri calcService = new Uri(ConfigurationManager.AppSettings["uri"]);
    Binding calcBinding = new BasicHttpBinding(BasicHttpSecurityMode.None);
    EndpointAddress calcEndpoint = new EndpointAddress(calcService.AbsoluteUri);


    CalculatorClient calcClient = new CalculatorClient(calcBinding, calcEndpoint);
    double sum = calcClient.Add(10, 20);
    double difference = calcClient.Subtract(sum, 10);

    Console.WriteLine("10 + 20 = {0}", sum.ToString());
    Console.WriteLine("{0} - 10 = {1}", sum.ToString(), difference.ToString());

    Console.ReadLine();
}

That should do it. So you can make the parameters to the Binding and EndpointAddress constructors read in from your configuration file (or user entry), and you can set additional Binding and EndpointAddress properties as you see fit.

Hopefully this helps. Let me know if there are additional questions and I'll update my answer accordingly.

UPDATE 2 (now with WSHttpBinding!!) I updated this to include using WSHttpBinding (with message level security) as a second example. There's a lot of different ways to handle security with WCF, and MSDN has a nice guide on configuring your security accordingly based upon your scenario. Here's the link to that page.

So my updated example is basically the same as above, except the client creates a WSHttpBinding instead of BasicHttpBinding, and specifies Message level security as the SecurityMode.

static void Main(string[] args)
{
    //refresh the appSettings section
    ConfigurationManager.RefreshSection("appSettings");

    //this could come from app.configs appSettings (value = "http://localhost:8000/ServiceModelSamples/Service/CalculatorService")
    Uri calcService = new Uri(ConfigurationManager.AppSettings["uri"]);

    //create the WsHttpBinding and set some security settings for the transport...
    WSHttpBinding calcBinding = new WSHttpBinding(SecurityMode.Message);
    EndpointAddress calcEndpoint = new EndpointAddress(calcService.AbsoluteUri);


    CalculatorClient calcClient = new CalculatorClient(calcBinding, calcEndpoint);
    double sum = calcClient.Add(10, 20);
    double difference = calcClient.Subtract(sum, 10);

    Console.WriteLine("10 + 20 = {0}", sum.ToString());
    Console.WriteLine("{0} - 10 = {1}", sum.ToString(), difference.ToString());

    Console.ReadLine();
}

The only difference I made on the server was that I specified WSHttpBinding when adding my service endpoint. Again, I chose the binding defaults, but that MSDN link above will describe how to configure the server based on your security needs.

// Step 3 of the hosting procedure: Add a service endpoint.
selfHost.AddServiceEndpoint(
    typeof(ICalculator),
    new WSHttpBinding(),
    "CalculatorService");

I hope this helps! Just remember that anything you can do in WCF configuration you can do in code. There's a 1:1 relationship between configuration settings and code (basically, everything in configuration translates to some WCF class that you can use).

Good luck! Let me know if there are other questions.

David Hoerster
  • 28,421
  • 8
  • 67
  • 102
  • I've gotten this mostly working, but a few comments -- first, I tried the first #1, and it didn't work (this is what the other topic I linked to was about) It's too inflexible for my needs. Supposedly this is a bad solution anyways because it requires my executable to be run with administrator privileges. I also attempted your solution but had some problems with it -- please see above. Thanks for the continued help! + – adam_0 Aug 04 '10 at 16:57
  • I updated my answer to show the change I made to the service endpoint implementation. Sorry if I wasn't clear about that -- didn't mean to be confusing. I wanted to keep it super simple. – David Hoerster Aug 04 '10 at 17:02
  • I don't think you fully understand my problem. I have a service host already set up and I can connect to it via a client that uses the service reference and the app.config file. What I would like is to be able to connect to any service host of this type _just_ by giving my client it's URI. Your method looks up the URI in the endpoints in the app.config file, which wouldn't work for this method because my client app won't know what endpoints are out there - the endpoint URIs will be entered by the user. – adam_0 Aug 04 '10 at 17:27
  • My reading of the URI from the app.config was just an example of what I thought you were trying to do - so maybe I didn't understand that. But that URI doesn't have to come from an app.config - it could come from a custom XML file, a database, a text box that the user types into, etc. Just create the Uri using the source of the input -- `Uri calcService = new Uri(txtUri.Text);`. I'm sorry if I'm still not understanding. – David Hoerster Aug 04 '10 at 17:37
  • 1
    Or you could do this, and set the Endpoint with a string directly -- `EndpointAddress calcEndpoint = new EndpointAddress(txtUri.Text);` – David Hoerster Aug 04 '10 at 17:37
  • That's what I was hoping I could do, thanks... my problem now is security. My client can't use a BasicHttpBinding. The server currently in place uses the WSHttpBinding, and that's what I'd like to do, but I would like to avoid having the user enter a userPrincipalName and servicePrincipalName, which is the way I have my "static" version working right now. Any ideas? (and if you make a WSHttpBinding work in a new answer, I'll upvote and / or accept that too) – adam_0 Aug 04 '10 at 17:56
  • I updated my answer with a WSHttpBinding example (simple) but with a link to a good MSDN article that describes various security scenarios that hopefully help you out. I didn't want to add another answer -- if you think this answers your question, just mark my answer as accepted. I hope this helps you out. WCF is pretty awesome, but so much of it can be a black box at times. It can be very mysterious. Good luck - let me know if you have other questions! – David Hoerster Aug 04 '10 at 23:02