How do I perform a query and then a command based on the query's result with MediatR? - asp.net-core

I am attempting to use the MediatR library to implement a command pattern in my net core web API, however, I am unsure how to proceed.
I have a situation where when a user attempts to registers an account, the API should check the database for a company with a domain that matches that of the provided email address then attach the company id to the user object as a foreign key or return an error if no company exists with that domain.
I have all the necessary commands and handler to perform these separately:
GetCompanyByDomainHandler.cs
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Application.Application.Exceptions;
using Application.Domain.Entities;
using Application.Persistence;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Application.Application.Companies.Queries.GetCompanyByDomain
{
public class GetCompanyByDomainHandler
IRequestHandler<GetCompanyByDomainQuery, Company>
{
private readonly ApplicationDbContext _context;
public GetCompanyByDomainHandler(ApplicationDbContext context)
{
_context = context;
}
public async Task<Company> Handle(GetCompanyByDomainQuery request,
CancellationToken cancellationToken)
{
var company = await _context.Companies.Where(c => c.Domain ==
request.Domain).SingleOrDefaultAsync();
if (company != null) {
return company;
}
throw new NotFoundException(nameof(Company), request.Domain);
}
}
}
GetCompanyByDomainQuery.cs
using Application.Domain.Entities;
using MediatR;
namespace Application.Application.Companies.Queries.GetCompanyByDomain
{
public class GetCompanyByDomainQuery : IRequest<Company>
{
public string Domain { get; set; }
}
}
CreateUserCommand.cs
using MediatR;
namespace Application.Application.Users.Commands.CreateUser
{
public class CreateUserCommand : IRequest<int>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string ConfirmPassword { get; set; }
public int CompanyId { get; set; }
}
}
CreateUserCommandHandler.cs
using MediatR;
using Application.Domain.Entities.Identity;
using Microsoft.AspNetCore.Identity;
using System.Threading;
using System.Threading.Tasks;
using System;
namespace Application.Application.Users.Commands.CreateUser
{
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, int>
{
private readonly UserManager<User> _userManager;
public CreateUserCommandHandler(UserManager<User> userManager)
{
_userManager = userManager;
}
public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var entity = new User
{
FirstName = request.FirstName,
LastName = request.LastName,
Email = request.EmailAddress,
UserName = request.EmailAddress,
CompanyId = request.CompanyId
};
var createUserResult = await _userManager.CreateAsync(entity, request.Password);
if (createUserResult.Succeeded)
{
return entity.Id;
}
throw new Exception("failed to create user");
}
}
}
CreateUserCommandValidator.cs
using FluentValidation;
namespace Application.Application.Users.Commands.CreateUser
{
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
public CreateUserCommandValidator()
{
RuleFor(v => v.Password)
.Equal(v => v.ConfirmPassword).WithName("password").WithMessage("Passwords do not match");
RuleFor(v => v.ConfirmPassword)
.Equal(v => v.Password).WithName("confirmPassword").WithMessage("Passwords do not match");
RuleFor(v => v.EmailAddress)
.NotEmpty().WithName("emailAddress").WithMessage("Email Address is required")
.EmailAddress().WithName("emailAddress").WithMessage("Invalid email address");
RuleFor(v => v.FirstName)
.NotEmpty().WithName("firstName").WithMessage("First Name is required");
RuleFor(v => v.LastName)
.NotEmpty().WithName("lastName").WithMessage("Last Name is required");
}
}
}
AuthenticationController.cs
using System.Threading.Tasks;
using Application.Application.Users.Commands.CreateUser;
using Microsoft.AspNetCore.Mvc;
namespace Application.WebUI.Controllers
{
public class AuthenticationController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Register([FromBody] CreateUserCommand command)
{
return Ok(Mediator.Send(command));
}
}
}
But how do I make them part of a single request?

First of all, change your GetCompanyByDomainHandler to NOT throw an exception if the company isn't found.
A company not found isn't an exception, it's a result of a query. Just return null
See why
Now, you can get the result of the query, act upon it (rather than a try catch)
//todo: implement a way of getting the domain from an email address - regex, or string.split('.') ??
var domain = GetDomainFromEmail(command.EmailAddress);
//now get the company (or null, if it doesn't exist)
var getCompanyByDomainQuery = new GetCompanyByDomainQuery() { Domain = domain}
var company = await _mediator.SendAsync(getCompanyByDomainQuery);
//if theres a company, attach the id to the createUserCommand
if(company != null)
{
command.CompanyId = company.Id;
}
//now save the user
var createdUser = await _mediator.SendAsync(command);
You can either wrap this up in another Handler - or treat the API Action Method as the 'orchestrator' (my preference)

Many years later, having worked with MediatR a lot more, I have realised that my question is fundamentally wrong. You shouldn't call a command after a query. If you need to do that, then you should merge the two together as they are performing a single domain driven action. The GetDomainFromEmail should be something that the CreateUserCommand uses within its own Handler rather than it being its own query. It could indeed be its own query if an endpoint existed to get the domain from an email, and both the CreateUserCommand Handler and the GetDomainFromEmailQuery handler would utilise a common utility that actually extracts the domain from an email.

Related

How to return the created entity with MediatR

As per Command Query Separation principle, commands should return void.
I am using MediatR Command Request Handler to create an entity as follows. So how can I get back the created entity?
public class CreateCompanyCommand : IRequest
{
public string Name { get; set; } = default!;
}
public class CreateCompanyHandler : IRequestHandler<CreateCompanyCommand>
{
private readonly IRepository<Company> _repository;
public CreateCompanyHandler(IRepository<Company> repository)
{
_repository = repository;
}
public async Task<Unit> Handle(CreateCompanyCommand request, CancellationToken cancellationToken)
{
var newCompany = new Company(request.Name);
var createdItem = await _repository.AddAsync(newCompany);
return Unit.Value;
}
}
I saw this question as well as its answer here, but I am still not clear.
How can I return the createdItem? What should be the Unit.Value? Can I modify and return something custom on my own instead of Unit.Value?
If you really want the created value back, you can change your request / handler to return it:
public class CreateCompanyCommand : IRequest<Company>
{
public string Name { get; set; } = default!;
}
public class CreateCompanyHandler : IRequestHandler<CreateCompanyCommand, Company>
{
private readonly IRepository<Company> _repository;
public CreateCompanyHandler(IRepository<Company> repository)
{
_repository = repository;
}
public async Task<Unit> Handle(CreateCompanyCommand request, CancellationToken cancellationToken)
{
var newCompany = new Company(request.Name);
var createdItem = await _repository.AddAsync(newCompany);
return newCompany;
}
}
There's nothing forcing you to not do this - commands aren't supposed to return anything in pure CQRS, but mediatr doesn't force this on you.
An alternative would be to create the id in the command (and initialize the id in the caller, or ctor of the command)
public class CreateCompanyCommand : IRequest<Company>
{
public Guid Id {get; set;}
public string Name { get; set; } = default!;
}
Call mediatr how you have in your question, then query for the Company you just created.

SignalR Hub Connection Error With Flutter

I am unable to invoke my hub method in my asp.net core backend ,
this is my CommentHub in flutter:
import 'package:signalr_client/signalr_client.dart';
import 'package:logging/logging.dart';
import 'package:xperience/models/global.dart';
final serverUrl = "http://" + base + ":8070/CommentHub/";
final hubConnection = HubConnectionBuilder().withUrl(serverUrl).build();
class HubService {
static final hubProtLogger = Logger("SignalR - hub");
static final transportProtLogger = Logger("SignalR - transport");
static final connectionOptions = HttpConnectionOptions;
static final httpOptions = new HttpConnectionOptions(logger: transportProtLogger);
final hubConnection = HubConnectionBuilder().withUrl(serverUrl, options: h
httpOptions).configureLogging(hubProtLogger).build();
Future addUser(String UserId,String PostId) async {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((LogRecord rec) {
print('${rec.level.name}: ${rec.time}: ${rec.message}');
});
hubConnection.start().then((result) async {
final result=await hubConnection.invoke("AddUser",args:<Object>[UserId,PostId]);
print(result);
});
}
Future removeUser() async {
final result = await hubConnection.invoke("RemoveUser",args: <Object>[]);
print(result);
hubConnection.stop();
}
}
This is my CommentHub.cs class in asp.net core:
using System;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using Xperience.Data.Entities.Posts;
namespace Xperience.Hub
{
public class CommentHub : Microsoft.AspNetCore.SignalR.Hub
{
public static List<Connection> Connections = new List<Connection>();
public void AddUser(string UserId,string PostId) {
Connections.Add(new Connection
{
ConnectionId = Context.ConnectionId,
UserId = UserId,
PostId = int.Parse(PostId)
});
}
public void RemoveUser() {
var user = Connections.Find(x => x.ConnectionId == Context.ConnectionId);
Connections.Remove(user);
}
}
public class Connection {
public string ConnectionId { get; set; }
public string UserId { get; set; }
public int PostId { get; set; }
}
}
I want to call AddUser and RemoveUser whenever my user enters and leaves a page,but whenever i invoke addUser i get this error:
**[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: Invalid argument(s)
and Sometimes : 'GeneralError' is not a subtype of type 'Error' in type cast**
Though the connection is being achieved successfully:
HTTP send: url 'http://ipAddress:8070/CommentHub/?id=XAekJZiTdIqswThHnPHWRQ', method: 'POST' content: '{"type":6}'
I/flutter (17474): FINEST: 2020-04-15 15:54:48.840894: (SSE transport) request complete. Response status: 200.
This seems to be a problem with the signalR flutter client plugin. I am getting a similar error. I am having minimal code.
The error on my side comes from the start function
void startConnection() async {
hubConnection.start();
}
Therefore, after analyzing the error,i am convinced it is a plugin error.
You can check this Issue incase you figure it out.

How do I get the current logged in user ID in the ApplicationDbContext using Identity?

I have created a .net core 2.1 MVC application using the template in Visual Studio with the Identity preset (user accounts stored in the application) and I am trying to automate some auditing fields.
Basically what I'm trying to do is overriding the SaveChangesAsync() method so that whenever changes are made to an entity the current logged in user ID is set to the auditing property of CreatedBy or ModifiedBy properties that are created as shadow properties on the entity.
I have looked at what seems to be tons of answers and surprisingly none of them work for me. I have tried injecting IHttpContext, HttpContext, UserManager, and I either can't seem to access a method that returns the user ID or I get a circular dependency error which I don't quite understand why it is happening.
I'm really running desperate with this one. I think something like this should be really straightforward to do, but I'm having a real hard time figuring out how to do it. There seem to be well documented solutions for web api controllers or for MVC controllers but not for use inside the ApplicationDbContext.
If someone can help me or at least point me into the right direction I'd be really grateful, thanks.
Let's call it DbContextWithUserAuditing
public class DBContextWithUserAuditing : IdentityDbContext<ApplicationUser, ApplicationRole, string>
{
public string UserId { get; set; }
public int? TenantId { get; set; }
public DBContextWithUserAuditing(DbContextOptions<DBContextWithUserAuditing> options) : base(options) { }
// here we declare our db sets
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.NamesToSnakeCase(); // PostgreSQL
modelBuilder.EnableSoftDelete();
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
ChangeTracker.ProcessModification(UserId);
ChangeTracker.ProcessDeletion(UserId);
ChangeTracker.ProcessCreation(UserId, TenantId);
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
ChangeTracker.DetectChanges();
ChangeTracker.ProcessModification(UserId);
ChangeTracker.ProcessDeletion(UserId);
ChangeTracker.ProcessCreation(UserId, TenantId);
return (await base.SaveChangesAsync(true, cancellationToken));
}
}
Then you have request pipeline and what you need - is a filter hook where you set your UserID
public class AppInitializerFilter : IAsyncActionFilter
{
private DBContextWithUserAuditing _dbContext;
public AppInitializerFilter(
DBContextWithUserAuditing dbContext
)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next
)
{
string userId = null;
int? tenantId = null;
var claimsIdentity = (ClaimsIdentity)context.HttpContext.User.Identity;
var userIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdClaim != null)
{
userId = userIdClaim.Value;
}
var tenantIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == CustomClaims.TenantId);
if (tenantIdClaim != null)
{
tenantId = !string.IsNullOrEmpty(tenantIdClaim.Value) ? int.Parse(tenantIdClaim.Value) : (int?)null;
}
_dbContext.UserId = userId;
_dbContext.TenantId = tenantId;
var resultContext = await next();
}
}
You activate this filter in the following way (Startup.cs file)
services
.AddMvc(options =>
{
options.Filters.Add(typeof(OnRequestInit));
})
Your app is then able to automatically set UserID & TenantID to newly created records
public static class ChangeTrackerExtensions
{
public static void ProcessCreation(this ChangeTracker changeTracker, string userId, int? tenantId)
{
foreach (var item in changeTracker.Entries<IHasCreationTime>().Where(e => e.State == EntityState.Added))
{
item.Entity.CreationTime = DateTime.Now;
}
foreach (var item in changeTracker.Entries<IHasCreatorUserId>().Where(e => e.State == EntityState.Added))
{
item.Entity.CreatorUserId = userId;
}
foreach (var item in changeTracker.Entries<IMustHaveTenant>().Where(e => e.State == EntityState.Added))
{
if (tenantId.HasValue)
{
item.Entity.TenantId = tenantId.Value;
}
}
}
I wouldn't recommend injecting HttpContext, UserManager or anything into your DbContext class because this way you violate Single Responsibility Principle.
Thanks to all the answers. In the end I decided to create a UserResolveService that receives through DI the HttpContextAccessor and can then get the current user's name. With the name I can then query the database to get whatever information I may need. I then inject this service on the ApplicationDbContext.
IUserResolveService.cs
public interface IUserResolveService
{
Task<string> GetCurrentSessionUserId(IdentityDbContext dbContext);
}
UserResolveService.cs
public class UserResolveService : IUserResolveService
{
private readonly IHttpContextAccessor httpContextAccessor;
public UserResolveService(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public async Task<string> GetCurrentSessionUserId(IdentityDbContext dbContext)
{
var currentSessionUserEmail = httpContextAccessor.HttpContext.User.Identity.Name;
var user = await dbContext.Users
.SingleAsync(u => u.Email.Equals(currentSessionUserEmail));
return user.Id;
}
}
You have to register the service on startup and inject it on the ApplicationDbContext and you can use it like this:
ApplicationDbContext.cs
var dbContext = this;
var currentSessionUserId = await userResolveService.GetCurrentSessionUserId(dbContext);

Change injected object at runtime

I want to have multiples implementation of the IUserRepository each implementation will work with a database type either MongoDB or any SQL database. To do this I have ITenant interface that have a connection string and other tenant configuration. The tenant is been injected into IUserRepository either MongoDB or any SQLDB implementation. What I need to know is how properly change the injected repository to choose the database base on the tenant.
Interfaces
public interface IUserRepository
{
string Login(string username, string password);
string Logoff(Guid id);
}
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public interface ITenant
{
string CompanyName { get; }
string ConnectionString { get; }
string DataBaseName { get; }
string EncriptionKey { get; }
}
Is important to know that the tenant id is been pass to an API via header request
StartUp.cs
// set inject httpcontet to the tenant implemantion
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
// inject tenant
services.AddTransient<ITenant, Tenant>();
// inject mongo repository but I want this to be programmatically
services.AddTransient<IUserRepository, UserMongoRepository>();
Sample Mongo Implementation
public class UserMongoRepository : IUserRepository
{
protected ITenant Tenant
public UserMongoRepository(ITenant tenant) :
base(tenant)
{
this.Tenant = tenant;
}
public string Login(string username, string password)
{
var query = new QueryBuilder<User>().Where(x => x.Username == username);
var client = new MongoClient(this.Tenant.ConnectionString);var server = client.GetServer();
var database = client.GetServer().GetDatabase(this.Tenant.DataBaseName);
var user = database.GetCollection<User>.FindAs<User>(query).AsQueryable().FirstOrDefault();
if (user == null)
throw new Exception("invalid username or password");
if (user.Password != password)
throw new Exception("invalid username or password");
return "Sample Token";
}
public string Logoff(Guid id)
{
throw new NotImplementedException();
}
}
Tenant
public class Tenant : ITenant
{
protected IHttpContextAccessor Accesor;
protected IConfiguration Configuration;
public Tenant(IHttpContextAccessor accesor, IDBConfiguration config)
{
this.Accesor = accesor;
this.Configuration = new Configuration().AddEnvironmentVariables();
if (!config.IsConfigure)
config.ConfigureDataBase();
}
private string _CompanyName;
public string CompanyName
{
get
{
if (string.IsNullOrWhiteSpace(_CompanyName))
{
_CompanyName = this.Accesor.Value.Request.Headers["Company"];
if (string.IsNullOrWhiteSpace(_CompanyName))
throw new Exception("Invalid Company");
}
return _CompanyName;
}
}
private string _ConnectionString;
public string ConnectionString
{
get
{
if (string.IsNullOrWhiteSpace(_ConnectionString))
{
_ConnectionString = this.Configuration.Get(this.CompanyName + "_" + "ConnectionString");
if (string.IsNullOrWhiteSpace(_ConnectionString))
throw new Exception("Invalid ConnectionString Setup");
}
return _ConnectionString;
}
}
private string _EncriptionKey;
public string EncriptionKey
{
get
{
if (string.IsNullOrWhiteSpace(_EncriptionKey))
{
_EncriptionKey = this.Configuration.Get(this.CompanyName + "_" + "EncriptionKey");
if (string.IsNullOrWhiteSpace(_EncriptionKey))
throw new Exception("Invalid Company Setup");
}
return _EncriptionKey;
}
}
private string _DataBaseName;
public string DataBaseName
{
get
{
if (string.IsNullOrWhiteSpace(_DataBaseName))
{
_DataBaseName = this.Configuration.Get(this.CompanyName + "_" + "DataBaseName");
if (string.IsNullOrWhiteSpace(_DataBaseName))
throw new Exception("Invalid Company Setup");
}
return _DataBaseName;
}
}
}
Controller
public class UsersController : Controller
{
protected IUserRepository DataService;
public UsersController(IUserRepository dataService)
{
this.DataService = dataService;
}
// the controller implematation
}
You should define a proxy implementation for IUserRepository and hide the actual implementations behind this proxy and at runtime decide which repository to forward the call to. For instance:
public class UserRepositoryDispatcher : IUserRepository
{
private readonly Func<bool> selector;
private readonly IUserRepository trueRepository;
private readonly IUserRepository falseRepository;
public UserRepositoryDispatcher(Func<bool> selector,
IUserRepository trueRepository, IUserRepository falseRepository) {
this.selector = selector;
this.trueRepository = trueRepository;
this.falseRepository = falseRepository;
}
public string Login(string username, string password) {
return this.CurrentRepository.Login(username, password);
}
public string Logoff(Guid id) {
return this.CurrentRepository.Logoff(id);
}
private IRepository CurrentRepository {
get { return selector() ? this.trueRepository : this.falseRepository;
}
}
Using this proxy class you can easily create a runtime predicate that decides which repository to use. For instance:
services.AddTransient<IUserRepository>(c =>
new UserRepositoryDispatcher(
() => c.GetRequiredService<ITenant>().DataBaseName.Contains("Mongo"),
trueRepository: c.GetRequiredService<UserMongoRepository>()
falseRepository: c.GetRequiredService<UserSqlRepository>()));
You can try injecting a factory rather than the actual repository. The factory will be responsible for building the correct repository based on the current user identity.
It might require a little more boiler plate code but it can achieve what you want. A little bit of inheritance might even make the controller code simpler.

WCF Data Service return Complex Type

I created a ComplexType and am returning it in a service operation as shown here:
[WebGet]
public IQueryable<ComplexAddressType> GetCityByZip(string zip)
{
List<AddressType> normalizeAddress = NormalizeAddressProcess(new AddressType
{
ZIP = zip,
}).AddressList;
return normalizeAddress.Select(x =>new ComplexAddressType
{
ZIP = x.zip,
City = x.City,
State = x.State
}).AsQueryable();
}
When I try to invoke the service operation by calling http://localhost/MyService.svc/GetCityByZip?zip='20000', the service operation invocation works and the browser displays a list of cities.
When I try to invoke the service operation by calling http://localhost/MyService.svc/GetCityByZip?zip='20000'&$top=1, the browser displays an error page.
Could you can help me?
Assuming ComplexAddressType is actually a complex type, you cannot use the $top system query option with that service operation. If you enable verbose errors per the comment above, you are likely getting back this error:
Query options $orderby, $inlinecount, $skip and $top cannot be applied to the requested resource.
To be able to use $top with the service operation, you will need to return a collection of entity types rather than complex types.
You could also just introduce another parameter to your function call, so that you can use a URL such as the following:
http://localhost:59803/ScratchService.svc/GetProfiles?startsWith='ABC'&top=2
Sample code:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Services;
using System.Data.Services.Common;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace Scratch.Web
{
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class ScratchService : DataService<ScratchContext>
{
static ScratchService()
{
Database.SetInitializer(new ScratchContextInitializer());
}
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
config.UseVerboseErrors = true;
}
[WebGet]
public IQueryable<User> GetUsers(int numUsers)
{
var users = new List<User>();
for (int i = 0; i < numUsers; i++)
{
users.Add(new User
{
Id = i,
Password = i.ToString(),
Username = i.ToString()
});
}
return users.AsQueryable();
}
[WebGet]
public IQueryable<Profile> GetProfiles(string startsWith, int top)
{
var profiles = new List<Profile>
{
new Profile{ DisplayName = "A", Preferences = "1" },
new Profile{ DisplayName = "AB", Preferences = "2" },
new Profile{ DisplayName = "ABC", Preferences = "3" },
new Profile{ DisplayName = "ABCD", Preferences = "4" },
new Profile{ DisplayName = "ABCDE", Preferences = "5" },
new Profile{ DisplayName = "ABCDEF", Preferences = "6" },
new Profile{ DisplayName = "ABCDEFG", Preferences = "7" }
};
return profiles.Where(p => p.DisplayName.StartsWith(startsWith)).Take(top).AsQueryable();
}
}
public class ScratchContextInitializer : DropCreateDatabaseAlways<ScratchContext>
{
}
public class ScratchContext : DbContext
{
public DbSet<User> Users { get; set; }
}
public class Profile
{
public string DisplayName { get; set; }
public string Preferences { get; set; }
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public Profile Profile { get; set; }
}
}
Last code will work when GetCityByZip method has 2 parameters. First one for zip and second one for top. In your case you have parameters inconsistency and wcf can't find method.