Our team decided to use Domain Driven Design architecture for our project. Now the discussion is going on for, "can we use ASP.NET Identity in DDD?".
Is there any disadvantages on using ASP.NET identity in DDD design.
I'm in a confusion to make a decision on it.
I have searched for it, but I didn't get any idea.
Any help would be appreciable.
The questions reveals several misconceptions:
It appears that you perceive the domain model as some monolithic model where you put every piece of application in. Instead, concentrate on strategic patterns to distinguish Bounded Contexts. Consider the domain as a composition of several loosely interconnected components. Then identify what your core domain is and apply DDD tactical patterns there. Not every ccomponent needs DDD. Some of them even should not use DDD. Especially - generic domains, like Authentication.
DDD is technology agnostic (to some point) so yes, you can use ASP.NET Identity or whatever library you like.
Authentication typically belongs to the Application layer, not Domain layer.
However - if in your domain there is a concept of a user/client/person, it might be required to use the identity provided by the identity component. But you have to understand that the meaning of User in your bounded context is different than meaning of User in Identity component. These are not the same concept. Although they both refer to the same physical person sitting somewhere and clicking on your app's GUI, they are 2 different models (or projections) of him that serve different purposes. So you shouldn't just reuse the ASP.NET User class in your bounded context.
Instead - separate contexts should communicate via an anticorruption layer. Basically you have to make some service (interface only) in your bounded context that produces context-specific User objects. The implementation of that interface made in infrastructure layer will be a wrapper of ASP.NET Identity that gets ASP.NET Identity user and produce corresponding bounded context's user.
I am new to DDD. But I achieved integration with Identity and DDD. Although I doubt it truly sticks to the priciples of DDD.
I started with the Entity:
public partial class User : IdentityUser {
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<User> manager) {
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
/// <summary>
/// The Date the User Registered for general information purposes
/// </summary>
public DateTime DateRegistered { get; set; }
}
Then the Interface:
public interface IUserRepository:IBaseRepository<User> {
/// <summary>
/// Register User to Identity Database
/// </summary>
/// <param name="userManager">User Manager to Handle Registration</param>
/// <param name="user">User to add to database</param>
/// <param name="password">User's password</param>
/// <returns>Identity Result</returns>
Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password);
/// <summary>
/// Login User
/// </summary>
/// <param name="signinManager">Signin Manager to handle login</param>
/// <param name="email">Email of user</param>
/// <param name="password">Password of user</param>
/// <param name="rememberMe">Boolean if the user wants to be remembered</param>
/// <returns>SignIn Status</returns>
Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe);
/// <summary>
/// Verify that code sent to User is valid
/// </summary>
/// <param name="signinManager">Signin Manager to handle verification</param>
/// <param name="provider">Provider of the code</param>
/// <param name="code">The code</param>
/// <param name="rememberMe">Boolean if user wants to be remembered</param>
/// <param name="rememberBrowser">Boolean if browser should be remembered</param>
/// <returns>SignIn Status</returns>
Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser);
/// <summary>
/// Confirm email of User
/// </summary>
/// <param name="userManager">User Manager to handle confirmation</param>
/// <param name="userId">String user Id of the User</param>
/// <param name="code">User code sent in Email</param>
/// <returns>Identity Result</returns>
Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code);
void ForgotPassword();
void ForgotPasswordConfirmation();
void ResetPassword();
void ResetPasswordConfirmation();
void ExternalLogin();
void SendCode();
void ExternalLoginCallback();
void ExternalLoginConfirmation();
/// <summary>
/// Log off user from the Application
/// </summary>
/// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
void Logoff(IAuthenticationManager AuthenticationManager);
/// <summary>
/// Get user based on their Email
/// </summary>
/// <param name="Email">Email of user</param>
/// <returns>User</returns>
User GetUser(string Email);
/// <summary>
/// Get User by their GUID
/// </summary>
/// <param name="ID">GUID</param>
/// <returns>User</returns>
User GetUserById(string ID);
}
Then the Repository:
public class UserRepository : BaseRepository<User>, IUserRepository {
/// <summary>
/// Confirm email of User
/// </summary>
/// <param name="userManager">User Manager to handle confirmation</param>
/// <param name="userId">String user Id of the User</param>
/// <param name="code">User code sent in Email</param>
/// <returns>Identity Result</returns>
public async Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code) =>
await userManager.ConfirmEmailAsync(userId, code);
public void ExternalLogin() {
throw new NotImplementedException();
}
public void ExternalLoginCallback() {
throw new NotImplementedException();
}
public void ExternalLoginConfirmation() {
throw new NotImplementedException();
}
public void ForgotPassword() {
throw new NotImplementedException();
}
public void ForgotPasswordConfirmation() {
throw new NotImplementedException();
}
/// <summary>
/// Get user based on their Email
/// </summary>
/// <param name="Email">Email of user</param>
/// <returns>User</returns>
public User GetUser(string Email) =>
_context.Users.Where(p => p.Email == Email).FirstOrDefault();
/// <summary>
/// Get User by their GUID
/// </summary>
/// <param name="ID">GUID</param>
/// <returns>User</returns>
public User GetUserById(string ID) =>
_context.Users.Find(ID);
/// <summary>
/// Login User
/// </summary>
/// <param name="signinManager">Signin Manager to handle login</param>
/// <param name="email">Email of user</param>
/// <param name="password">Password of user</param>
/// <param name="rememberMe">Boolean if the user wants to be remembered</param>
/// <returns>SignIn Status</returns>
public async Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe) =>
await signinManager.PasswordSignInAsync(email, password, rememberMe, shouldLockout: false);
/// <summary>
/// Log off user from the Application
/// </summary>
/// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
public void Logoff(IAuthenticationManager AuthenticationManager) {
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
}
/// <summary>
/// Register User to Identity Database
/// </summary>
/// <param name="userManager">User Manager to Handle Registration</param>
/// <param name="user">User to add to database</param>
/// <param name="password">User's password</param>
/// <returns>Identity Result</returns>
public async Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password) =>
await userManager.CreateAsync(user, password);
public void ResetPassword() {
throw new NotImplementedException();
}
public void ResetPasswordConfirmation() {
throw new NotImplementedException();
}
public void SendCode() {
throw new NotImplementedException();
}
/// <summary>
/// Verify that code sent to User is valid
/// </summary>
/// <param name="signinManager">Signin Manager to handle verification</param>
/// <param name="provider">Provider of the code</param>
/// <param name="code">The code</param>
/// <param name="rememberMe">Boolean if user wants to be remembered</param>
/// <param name="rememberBrowser">Boolean if browser should be remembered</param>
/// <returns>SignIn Status</returns>
public async Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser) =>
await signinManager.TwoFactorSignInAsync(provider, code, isPersistent: rememberMe, rememberBrowser: rememberBrowser);
}
IService:
public interface IUserService {
/// <summary>
/// Register User to Identity Database
/// </summary>
/// <param name="userManager">User Manager to Handle Registration</param>
/// <param name="user">User to add to database</param>
/// <param name="password">User's password</param>
/// <returns></returns>
Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password);
/// <summary>
/// Login User
/// </summary>
/// <param name="signinManager">Signin Manager to handle login</param>
/// <param name="email">Email of user</param>
/// <param name="password">Password of user</param>
/// <param name="rememberMe">Boolean if the user wants to be remembered</param>
/// <returns></returns>
Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe);
/// <summary>
/// Verify that code sent to User is valid
/// </summary>
/// <param name="signinManager">Signin Manager to handle verification</param>
/// <param name="provider">Provider of the code</param>
/// <param name="code">The code</param>
/// <param name="rememberMe">Boolean if user wants to be remembered</param>
/// <param name="rememberBrowser">Boolean if browser should be remembered</param>
/// <returns></returns>
Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser);
/// <summary>
/// Confirm email of User
/// </summary>
/// <param name="userManager">User Manager to handle confirmation</param>
/// <param name="userId">String user Id of the User</param>
/// <param name="code">User code sent in Email</param>
/// <returns></returns>
Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code);
void ForgotPassword();
void ForgotPasswordConfirmation();
void ResetPassword();
void ResetPasswordConfirmation();
void ExternalLogin();
void SendCode();
void ExternalLoginCallback();
void ExternalLoginConfirmation();
/// <summary>
/// Log off user from the Application
/// </summary>
/// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
void Logoff(IAuthenticationManager AuthenticationManager);
/// <summary>
/// Get user based on their Email
/// </summary>
/// <param name="Email">Email of user</param>
/// <returns>User</returns>
User GetUser(string Email);
/// <summary>
/// Get User by their GUID
/// </summary>
/// <param name="ID">GUID</param>
/// <returns>User</returns>
User GetUserById(string ID);
}
Service:
public class UserService : ServiceBase, IUserService {
#region Private Field
private IUserRepository _userRepository;
#endregion
#region Constructor
/// <summary>
/// Constructor to initialise User Repository
/// </summary>
/// <param name="userRepository"></param>
public UserService(IUserRepository userRepository) {
_userRepository = userRepository;
}
#endregion
#region Methods
/// <summary>
/// Confirm email of User
/// </summary>
/// <param name="userManager">User Manager to handle confirmation</param>
/// <param name="userId">String user Id of the User</param>
/// <param name="code">User code sent in Email</param>
/// <returns>Identity Result</returns>
public Task<IdentityResult> ConfirmEmail(UserManager<User, string> userManager, string userId, string code) =>
_userRepository.ConfirmEmail(userManager, userId, code);
public void ExternalLogin() {
throw new NotImplementedException();
}
public void ExternalLoginCallback() {
throw new NotImplementedException();
}
public void ExternalLoginConfirmation() {
throw new NotImplementedException();
}
public void ForgotPassword() {
throw new NotImplementedException();
}
public void ForgotPasswordConfirmation() {
throw new NotImplementedException();
}
/// <summary>
/// Get user based on their Email
/// </summary>
/// <param name="Email">Email of user</param>
/// <returns>User</returns>
public User GetUser(string Email) {
throw new NotImplementedException();
}
/// <summary>
/// Get User by their GUID
/// </summary>
/// <param name="ID">GUID</param>
/// <returns>User</returns>
public User GetUserById(string ID) {
throw new NotImplementedException();
}
/// <summary>
/// Login User
/// </summary>
/// <param name="signinManager">Signin Manager to handle login</param>
/// <param name="email">Email of user</param>
/// <param name="password">Password of user</param>
/// <param name="rememberMe">Boolean if the user wants to be remembered</param>
/// <returns>SignIn Status</returns>
public Task<SignInStatus> Login(SignInManager<User, string> signinManager, string email, string password, bool rememberMe) =>
_userRepository.Login(signinManager, email, password, rememberMe);
/// <summary>
/// Log off user from the Application
/// </summary>
/// <param name="AuthenticationManager">Application Manager to handle Sign out</param>
public void Logoff(IAuthenticationManager AuthenticationManager) {
_userRepository.Logoff(AuthenticationManager);
}
/// <summary>
/// Register User to Identity Database
/// </summary>
/// <param name="userManager">User Manager to Handle Registration</param>
/// <param name="user">User to add to database</param>
/// <param name="password">User's password</param>
public Task<IdentityResult> Register(UserManager<User, string> userManager, User user, string password) =>
_userRepository.Register(userManager, user, password);
public void ResetPassword() {
throw new NotImplementedException();
}
public void ResetPasswordConfirmation() {
throw new NotImplementedException();
}
public void SendCode() {
throw new NotImplementedException();
}
/// <summary>
/// Verify that code sent to User is valid
/// </summary>
/// <param name="signinManager">Signin Manager to handle verification</param>
/// <param name="provider">Provider of the code</param>
/// <param name="code">The code</param>
/// <param name="rememberMe">Boolean if user wants to be remembered</param>
/// <param name="rememberBrowser">Boolean if browser should be remembered</param>
/// <returns>SignIn Status</returns>
public Task<SignInStatus> VerifyCode(SignInManager<User, string> signinManager, string provider, string code, bool rememberMe, bool rememberBrowser) =>
_userRepository.VerifyCode(signinManager, provider, code, rememberMe, rememberBrowser);
#endregion
}
You can use anything you like. But be aware of pollution particular solution is going to make. If your domain model gets messed up with hundreds of lines of asp.net technical kind of plumbing code that makes your domain logic hard to perceive and you are missing point of DDD.
In ideal situation - your domain model should depend only on programming language.
Also - you might find something useful from my long time ago implementation of user session related code.
Related
In order to be able to add controllers in my ASP.NET Core app, I can add either
services.AddControllersWithViews()
or
services.AddMvc()
in ConfigureServices method at Startup class.
It looks like both of them are working fine for me. I would like to learn which one is better under which circumstances?
As far as I know, services.AddMvc() was the older way, but still available.
If I keep using services.AddMvc() would it be a problem in the future?
The source code speaks for itself
MvcServiceCollectionExtensions.AddMvc()
/// <summary>
/// Adds MVC services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>An <see cref="IMvcBuilder"/> that can be used to further configure the MVC services.</returns>
public static IMvcBuilder AddMvc(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddControllersWithViews();
return services.AddRazorPages();
}
/// <summary>
/// Adds MVC services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="setupAction">An <see cref="Action{MvcOptions}"/> to configure the provided <see cref="MvcOptions"/>.</param>
/// <returns>An <see cref="IMvcBuilder"/> that can be used to further configure the MVC services.</returns>
public static IMvcBuilder AddMvc(this IServiceCollection services, Action<MvcOptions> setupAction)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
var builder = services.AddMvc();
builder.Services.Configure(setupAction);
return builder;
}
MvcServiceCollectionExtensions.AddControllersWithViews()
/// <summary>
/// Adds services for controllers to the specified <see cref="IServiceCollection"/>. This method will not
/// register services used for pages.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>An <see cref="IMvcBuilder"/> that can be used to further configure the MVC services.</returns>
/// <remarks>
/// <para>
/// This method configures the MVC services for the commonly used features with controllers with views. This
/// combines the effects of <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>,
/// <see cref="MvcApiExplorerMvcCoreBuilderExtensions.AddApiExplorer(IMvcCoreBuilder)"/>,
/// <see cref="MvcCoreMvcCoreBuilderExtensions.AddAuthorization(IMvcCoreBuilder)"/>,
/// <see cref="MvcCorsMvcCoreBuilderExtensions.AddCors(IMvcCoreBuilder)"/>,
/// <see cref="MvcDataAnnotationsMvcCoreBuilderExtensions.AddDataAnnotations(IMvcCoreBuilder)"/>,
/// <see cref="MvcCoreMvcCoreBuilderExtensions.AddFormatterMappings(IMvcCoreBuilder)"/>,
/// <see cref="TagHelperServicesExtensions.AddCacheTagHelper(IMvcCoreBuilder)"/>,
/// <see cref="MvcViewFeaturesMvcCoreBuilderExtensions.AddViews(IMvcCoreBuilder)"/>,
/// and <see cref="MvcRazorMvcCoreBuilderExtensions.AddRazorViewEngine(IMvcCoreBuilder)"/>.
/// </para>
/// <para>
/// To add services for pages call <see cref="AddRazorPages(IServiceCollection)"/>.
/// </para>
/// </remarks>
public static IMvcBuilder AddControllersWithViews(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
var builder = AddControllersWithViewsCore(services);
return new MvcBuilder(builder.Services, builder.PartManager);
}
/// <summary>
/// Adds services for controllers to the specified <see cref="IServiceCollection"/>. This method will not
/// register services used for pages.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="configure">An <see cref="Action{MvcOptions}"/> to configure the provided <see cref="MvcOptions"/>.</param>
/// <returns>An <see cref="IMvcBuilder"/> that can be used to further configure the MVC services.</returns>
/// <remarks>
/// <para>
/// This method configures the MVC services for the commonly used features with controllers with views. This
/// combines the effects of <see cref="MvcCoreServiceCollectionExtensions.AddMvcCore(IServiceCollection)"/>,
/// <see cref="MvcApiExplorerMvcCoreBuilderExtensions.AddApiExplorer(IMvcCoreBuilder)"/>,
/// <see cref="MvcCoreMvcCoreBuilderExtensions.AddAuthorization(IMvcCoreBuilder)"/>,
/// <see cref="MvcCorsMvcCoreBuilderExtensions.AddCors(IMvcCoreBuilder)"/>,
/// <see cref="MvcDataAnnotationsMvcCoreBuilderExtensions.AddDataAnnotations(IMvcCoreBuilder)"/>,
/// <see cref="MvcCoreMvcCoreBuilderExtensions.AddFormatterMappings(IMvcCoreBuilder)"/>,
/// <see cref="TagHelperServicesExtensions.AddCacheTagHelper(IMvcCoreBuilder)"/>,
/// <see cref="MvcViewFeaturesMvcCoreBuilderExtensions.AddViews(IMvcCoreBuilder)"/>,
/// and <see cref="MvcRazorMvcCoreBuilderExtensions.AddRazorViewEngine(IMvcCoreBuilder)"/>.
/// </para>
/// <para>
/// To add services for pages call <see cref="AddRazorPages(IServiceCollection)"/>.
/// </para>
/// </remarks>
public static IMvcBuilder AddControllersWithViews(this IServiceCollection services, Action<MvcOptions> configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
// This method excludes all of the view-related services by default.
var builder = AddControllersWithViewsCore(services);
if (configure != null)
{
builder.AddMvcOptions(configure);
}
return new MvcBuilder(builder.Services, builder.PartManager);
}
As you can see AddMvc is basically wrapping a call to AddControllersWithViews, with the addition of calling AddRazorPages.
If I keep using services.AddMvc() would it be a problem in the future?
There is really no way to answer that accurately.
Follow the advice provided by currently available documentation to avoid any unwanted behavior
i have an action on a controller which calls
var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme);
i'm trying to mock this result in a unit test like so
httpContextMock.AuthenticateAsync(Arg.Any<string>()).Returns(AuthenticateResult.Success(...
however that throws an InvalidOperationException
"No service for type 'Microsoft.AspNetCore.Authentication.IAuthenticationService' has been registered"
what is the correct way to mock this method?
That extension method
/// <summary>
/// Extension method for authenticate.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/> context.</param>
/// <param name="scheme">The name of the authentication scheme.</param>
/// <returns>The <see cref="AuthenticateResult"/>.</returns>
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);
goes through the IServiceProvider RequestServices property.
/// <summary>
/// Gets or sets the <see cref="IServiceProvider"/> that provides access to the request's service container.
/// </summary>
public abstract IServiceProvider RequestServices { get; set; }
Mock the service provider to return a mocked IAuthenticationService and you should be able to fake your way through the test.
authServiceMock.AuthenticateAsync(Arg.Any<HttpContext>(), Arg.Any<string>())
.Returns(Task.FromResult(AuthenticateResult.Success()));
providerMock.GetService(typeof(IAuthenticationService))
.Returns(authServiceMock);
httpContextMock.RequestServices.Returns(providerMock);
//...
I am trying to get my Dialog to work with my database.
If I have my dialog like this:
[Serializable]
public class QuestionDialog : IDialog<object>
{
/// <summary>
/// Start our response
/// </summary>
/// <param name="context">The current context</param>
/// <returns></returns>
public async Task StartAsync(IDialogContext context)
{
// Move to the next method
context.Wait(StepOneAsync);
}
/// <summary>
/// When our message is recieved we execute this delegate
/// </summary>
/// <param name="context">The current context</param>
/// <param name="result">The result object</param>
/// <returns></returns>
private async Task StepOneAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
// Get our activity
var activity = await result;
// Ask our first question
await context.PostAsync("hi");
// Get our answer
context.Done(this);
}
}
Everything works fine and I get my message as expected. I then changed it to this:
[Serializable]
public class QuestionDialog : IDialog<object>
{
// Private properties
private IList<QuestionGroup> _questionGroups;
/// <summary>
/// Start our response
/// </summary>
/// <param name="context">The current context</param>
/// <returns></returns>
public async Task StartAsync(IDialogContext context)
{
try
{
// Create our service
var questionGroupService = new QuestionGroupService(new UnitOfWork<DatabaseContext>());
// Add our question groups
this._questionGroups = await questionGroupService.ListAllAsync();
// Move to the next method
context.Wait(StepOneAsync);
} catch (Exception ex)
{
}
}
/// <summary>
/// When our message is recieved we execute this delegate
/// </summary>
/// <param name="context">The current context</param>
/// <param name="result">The result object</param>
/// <returns></returns>
private async Task StepOneAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
// Get our activity
var activity = await result;
// Ask our first question
await context.PostAsync("hi");
// Get our answer
context.Done(this);
}
}
And it doesn't go the the StepOneAsync method. Can anyone see anything glaringly obvious as to why this isn't working?
Make sure your QestionGroup model is marked as Serializable.
If you cannot make it serializable and you still want to reference it during your dialog, you need to go with one of the alternatives described in the "How can I reference non-serializable services from my C# dialogs?" section of the Bot Framework Technical FAQ.
The simplest one is to use the NonSerialized attribute in your field.
Alternatively, you can try using registering the Reflection Serialization Surrogate by adding it to the Autofac container. In your global.asax, try adding this code:
var builder = new ContainerBuilder();
builder.RegisterModule(new ReflectionSurrogateModule());
builder.Update(Conversation.Container);
So in C# I am trying to access a file on a network, for example at "//applications/myapp/test.txt", as follows:
const string fileLocation = #"//applications/myapp/test.txt";
using (StreamReader fin = new StreamReader(FileLocation))
{
while(!fin.EndOfStream()){
//Do some cool stuff with file
}
}
However I get the following error:
System.IO.IOException : Logon failure: unknown user name or bad password.
I figure its because I need to supply some network credentials but I'm not sure how to get those to work in this situation.
Does anyone know the best way (or any way) to gain access to these files that are on a a password protected location?
Thanks in advance!!
This question got me to where I needed to be pretty quickly in the same case.
Here's how I adapted the code:
using System;
using System.Runtime.InteropServices;
/// <summary>
/// Implements P/Invoke Interop calls to the operating system.
/// </summary>
internal static class NativeMethods
{
/// <summary>
/// The type of logon operation to perform.
/// </summary>
internal enum LogonType : int
{
/// <summary>
/// This logon type is intended for users who will be interactively
/// using the computer, such as a user being logged on by a
/// terminal server, remote shell, or similar process.
/// This logon type has the additional expense of caching logon
/// information for disconnected operations; therefore, it is
/// inappropriate for some client/server applications, such as a
/// mail server.
/// </summary>
Interactive = 2,
/// <summary>
/// This logon type is intended for high performance servers to
/// authenticate plaintext passwords.
/// The LogonUser function does not cache credentials for this
/// logon type.
/// </summary>
Network = 3,
/// <summary>
/// This logon type is intended for batch servers, where processes
/// may be executing on behalf of a user without their direct
/// intervention. This type is also for higher performance servers
/// that process many plaintext authentication attempts at a time,
/// such as mail or Web servers.
/// The LogonUser function does not cache credentials for this
/// logon type.
/// </summary>
Batch = 4,
/// <summary>
/// Indicates a service-type logon. The account provided must have
/// the service privilege enabled.
/// </summary>
Service = 5,
/// <summary>
/// This logon type is for GINA DLLs that log on users who will be
/// interactively using the computer.
/// This logon type can generate a unique audit record that shows
/// when the workstation was unlocked.
/// </summary>
Unlock = 7,
/// <summary>
/// This logon type preserves the name and password in the
/// authentication package, which allows the server to make
/// connections to other network servers while impersonating the
/// client. A server can accept plaintext credentials from a
/// client, call LogonUser, verify that the user can access the
/// system across the network, and still communicate with other
/// servers.
/// NOTE: Windows NT: This value is not supported.
/// </summary>
NetworkCleartext = 8,
/// <summary>
/// This logon type allows the caller to clone its current token
/// and specify new credentials for outbound connections. The new
/// logon session has the same local identifier but uses different
/// credentials for other network connections.
/// NOTE: This logon type is supported only by the
/// LOGON32_PROVIDER_WINNT50 logon provider.
/// NOTE: Windows NT: This value is not supported.
/// </summary>
NewCredentials = 9
}
/// <summary>
/// Specifies the logon provider.
/// </summary>
internal enum LogonProvider : int
{
/// <summary>
/// Use the standard logon provider for the system.
/// The default security provider is negotiate, unless you pass
/// NULL for the domain name and the user name is not in UPN format.
/// In this case, the default provider is NTLM.
/// NOTE: Windows 2000/NT: The default security provider is NTLM.
/// </summary>
Default = 0,
/// <summary>
/// Use this provider if you'll be authenticating against a Windows
/// NT 3.51 domain controller (uses the NT 3.51 logon provider).
/// </summary>
WinNT35 = 1,
/// <summary>
/// Use the NTLM logon provider.
/// </summary>
WinNT40 = 2,
/// <summary>
/// Use the negotiate logon provider.
/// </summary>
WinNT50 = 3
}
/// <summary>
/// The type of logon operation to perform.
/// </summary>
internal enum SecurityImpersonationLevel : int
{
/// <summary>
/// The server process cannot obtain identification information
/// about the client, and it cannot impersonate the client. It is
/// defined with no value given, and thus, by ANSI C rules,
/// defaults to a value of zero.
/// </summary>
Anonymous = 0,
/// <summary>
/// The server process can obtain information about the client,
/// such as security identifiers and privileges, but it cannot
/// impersonate the client. This is useful for servers that export
/// their own objects, for example, database products that export
/// tables and views. Using the retrieved client-security
/// information, the server can make access-validation decisions
/// without being able to use other services that are using the
/// client's security context.
/// </summary>
Identification = 1,
/// <summary>
/// The server process can impersonate the client's security
/// context on its local system. The server cannot impersonate the
/// client on remote systems.
/// </summary>
Impersonation = 2,
/// <summary>
/// The server process can impersonate the client's security
/// context on remote systems.
/// NOTE: Windows NT: This impersonation level is not supported.
/// </summary>
Delegation = 3
}
/// <summary>
/// Logs on the user.
/// </summary>
/// <param name="userName">Name of the user.</param>
/// <param name="domain">The domain.</param>
/// <param name="password">The password.</param>
/// <param name="logonType">Type of the logon.</param>
/// <param name="logonProvider">The logon provider.</param>
/// <param name="token">The token.</param>
/// <returns>True if the function succeeds, false if the function fails.
/// To get extended error information, call GetLastError.</returns>
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool LogonUser(
string userName,
string domain,
string password,
LogonType logonType,
LogonProvider logonProvider,
out IntPtr token);
/// <summary>
/// Duplicates the token.
/// </summary>
/// <param name="existingTokenHandle">The existing token
/// handle.</param>
/// <param name="securityImpersonationLevel">The security impersonation
/// level.</param>
/// <param name="duplicateTokenHandle">The duplicate token
/// handle.</param>
/// <returns>True if the function succeeds, false if the function fails.
/// To get extended error information, call GetLastError.</returns>
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DuplicateToken(
IntPtr existingTokenHandle,
SecurityImpersonationLevel securityImpersonationLevel,
out IntPtr duplicateTokenHandle);
/// <summary>
/// Closes the handle.
/// </summary>
/// <param name="handle">The handle.</param>
/// <returns>True if the function succeeds, false if the function fails.
/// To get extended error information, call GetLastError.</returns>
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(IntPtr handle);
}
followed by
IntPtr token;
if (!NativeMethods.LogonUser(
this.userName,
this.domain,
this.password,
NativeMethods.LogonType.NewCredentials,
NativeMethods.LogonProvider.Default,
out token))
{
throw new Win32Exception();
}
try
{
IntPtr tokenDuplicate;
if (!NativeMethods.DuplicateToken(
token,
NativeMethods.SecurityImpersonationLevel.Impersonation,
out tokenDuplicate))
{
throw new Win32Exception();
}
try
{
using (WindowsImpersonationContext impersonationContext =
new WindowsIdentity(tokenDuplicate).Impersonate())
{
// Do stuff with your share here.
impersonationContext.Undo();
return;
}
}
finally
{
if (tokenDuplicate != IntPtr.Zero)
{
if (!NativeMethods.CloseHandle(tokenDuplicate))
{
// Uncomment if you need to know this case.
////throw new Win32Exception();
}
}
}
}
finally
{
if (token != IntPtr.Zero)
{
if (!NativeMethods.CloseHandle(token))
{
// Uncomment if you need to know this case.
////throw new Win32Exception();
}
}
}
My solution in Visual Studio 2012 currently contains two projects:
DLL
WPF application (which requires methods of the DLL)
Both, the DLL and the WPF application, use NLog for logging. Currently each project contains the NLog DLL itself.
Here is what I don't understand:
It seems unnecessary to me including the identical NLog DLL in each project.
The DLL however shall be reusable in other solutions, i.e. somehow the NLog DLL must be contained in the DLL project.
What would be an adequate way of setting up the Visual Studio solution and/or projects?
well you need the DLL in all projects where you use it and surely you need it deployed with the binaries of the executable (WPF application in your case) so that it can be found and used at runtime.
what I tend to do in all my projects is create a wrapper around the logging engine so that I do not need to reference and depend on specific third party logging APIs, like Log4Net or NLog, so I use my wrapper logging class everywhere and then I have a reference to the logging asembly only in the wrapper class's project and in the executable project to have the assembly deployed to the bin folder.
hope this helps ;-)
If your DLL is just a core library you plan on sharing among various projects, it may be wise to add an NLog reference and wrapper code to just that library, then make sure that any consumer application (such as your WPF project) has an NLog.config file associated with it.
Since you're using VS2012 I'm assuming you're also most likely working with .NET 4.5 which allows you to take advantage of the new caller info attributes. I've written the following code below for a basic NLog wrapper and believe it has the perfect balance of efficiency (doesn't use StackTrace) and usability.
using System;
using System.Runtime.CompilerServices;
using NLog;
namespace ProjectName.Core.Utilities
{
/// <summary>
/// Generic NLog wrapper.
/// </summary>
public static class Logger
{
/// <summary>
/// Gets or sets the enabled status of the logger.
/// </summary>
public static bool Enabled
{
get { return LogManager.IsLoggingEnabled(); }
set
{
if (value)
{
while (!Enabled) LogManager.EnableLogging();
}
else
{
while (Enabled) LogManager.DisableLogging();
}
}
}
/// <summary>
/// Writes the diagnostic message at the Trace level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Trace(string message, Exception exception = null,
[CallerFilePath] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Trace, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Debug level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Debug(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Debug, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Info level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Info(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Info, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Warn level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Warn(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Warn, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Error level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Error(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Error, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the diagnostic message at the Fatal level.
/// </summary>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
public static void Fatal(string message, Exception exception = null,
[CallerFilePathAttribute] string callerPath = "",
[CallerMemberName] string callerMember = "",
[CallerLineNumber] int callerLine = 0)
{
Log(LogLevel.Fatal, message, exception, callerPath, callerMember, callerLine);
}
/// <summary>
/// Writes the specified diagnostic message.
/// </summary>
/// <param name="level"></param>
/// <param name="message"></param>
/// <param name="exception"></param>
/// <param name="callerPath"></param>
/// <param name="callerMember"></param>
/// <param name="callerLine"></param>
private static void Log(LogLevel level, string message, Exception exception = null, string callerPath = "", string callerMember = "", int callerLine = 0)
{
// get the source-file-specific logger
var logger = LogManager.GetLogger(callerPath);
// quit processing any further if not enabled for the requested logging level
if (!logger.IsEnabled(level)) return;
// log the event with caller information bound to it
var logEvent = new LogEventInfo(level, callerPath, message) {Exception = exception};
logEvent.Properties.Add("callerpath", callerPath);
logEvent.Properties.Add("callermember", callerMember);
logEvent.Properties.Add("callerline", callerLine);
logger.Log(logEvent);
}
}
}
Then try throwing this into the layout field of one of the targets in your NLog.config to grab the detailed caller information.
${event-context:item=callerpath}:${event-context:item=callermember}(${event-context:item=callerline})
You better abstract the use of your logging mechanism. I described this in this blog post, it's about log4net but is the same principle whatever framework you use. In any case, you need the log assembly in every project where you use it, but by abstracting it it's easy to replace it by something else (when testing for example). Logging is infrastructure, so you would put the interfaces and concrete implementation in an infrastructure project, and reference that one from the projects in which you want to log.