Using generic class with constraints to query from DocumentDB - c#

I tried to query an object from azure cosmos document DB. UserRepository is made generic so that the consumer can define its own User object when using the repository as long as it implements IUser interface.
Here is the UserRepository
public class UserRepository<T> : IUserRepository<T> where T : class, IUser
{
private readonly CosmosDbOptions _cosmosDBOptions;
private readonly DocumentDbHelper _documentDBHelper;
public UserRepository(
IOptions<CosmosDbOptions> cosmosDBOptions,
DocumentDbHelper documentDBHelper)
{
_cosmosDBOptions = cosmosDBOptions.Value;
_documentDBHelper = documentDBHelper;
}
public T GetUserByEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return default(T);
var user = _documentDBHelper
.GetItemsAsync<T>(u => ((T)u).Email == email, _cosmosDBOptions.DocumentDb.UserCollection, email)
.Result
.OrderBy(u => u.Modified)
.FirstOrDefault();
return user;
}
...
}
Here is what the DocumentDbHelper looks like
public class DocumentDbHelper
{
private readonly DocumentClient _client;
private readonly DocumentDbOptions _documentDBOptions;
public DocumentDbHelper(IOptions<CosmosDbOptions> documentDBOptions)
{
_cosmosDBOptions = documentDBOptions.Value.DocumentDb;
var serializerSettings = new JsonSerializerSettings
{
DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
DateParseHandling = DateParseHandling.DateTimeOffset
};
_client = new DocumentClient(
new Uri(_cosmosDBOptions.Endpoint), _cosmosDBOptions.AuthKey, serializerSettings);
}
public async Task<IEnumerable<T>> GetItemsAsync<T>(
Expression<Func<T, bool>> predicate, string collectionId, string partitionKey)
{
var query = _client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(_cosmosDBOptions.DatabaseId, collectionId),
new FeedOptions { PartitionKey = new PartitionKey(partitionKey) })
.Where(predicate)
.AsDocumentQuery();
var results = new List<T>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync<T>());
}
return results;
}
...
}
And here is how the UserRepository being used
public class UsersController : Controller
{
private readonly IUserRepository<User> _userRepository;
public UsersController(IUserRepository<User> userRepository)
{
_userRepository = userRepository;
}
public IActionResult Get(string email)
{
var user = _userRepository.GetUserByEmail(email);
return Ok(user);
}
}
Just to clarify again, the UserRepository is made generic so that other consumer can do something like this.
public class Users2Controller : Controller
{
private readonly IUserRepository<User2> _userRepository;
public Users2Controller(IUserRepository<User2> userRepository)
{
_userRepository = userRepository;
}
public IActionResult Get(string email)
{
var user = _userRepository.GetUserByEmail(email);
return Ok(user);
}
}
The problem is when executing UsersController.Get and it reaches GetItemsAsync of DocumentDbHelper, this method returns null, even though there is an object with a valid email address as passed to the controller action.
But when I change the constraints of UserRepository from where T : class, IUser to where T : User like below,
public class UserRepository<T> : IUserRepository<T> where T : User
the object is retrieved successfully.
Why is this? Is it possible to make this work by having the interface constraint?

Try to use it as mentioned below, this should work.
public class UserRepository<T> : IUserRepository<T> where T : IUser

I found the root cause.
Here is the snippet of User class:
public class User : IUser
{
[JsonProperty("id")]
public string DocumentId { get; set; } = "";
[JsonProperty("created")]
public DateTimeOffset Created { get; set; }
[JsonProperty("modified")]
public DateTimeOffset Modified { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
...
}
And here is how the IUser interface used to look:
public interface IUser
{
string DocumentId { get; set; }
DateTimeOffset Created { get; set; }
DateTimeOffset Modified { get; set; }
string Email { get; set; }
...
}
The problem is related to this https://github.com/Azure/azure-documentdb-dotnet/issues/317. Once I added JsonProperty attribute to the IUser interface props like below:
public interface IUser
{
[JsonProperty("id")]
string DocumentId { get; set; }
[JsonProperty("created")]
DateTimeOffset Created { get; set; }
[JsonProperty("modified")]
DateTimeOffset Modified { get; set; }
[JsonProperty("email")]
string Email { get; set; }
...
}
it works perfectly.

Related

Is there a better way to test this ASP.NET MVC application?

I have created mocks for both IUserService and IDataResult. The test works fine but instead of pulling from the database, I created a user object to test this getcustomerlogin method. Is there a better way to test this case? Can we test this method with actual data from the database?
This is the testing code:
namespace UnitTesting
{
public class Tests
{
[Test]
public void login_unit_test()
{
// arrange
var userinput = new UserForLogin()
{
email = "testmail#mail.com",
password = "123456"
};
var userobject = new User()
{
Email= "testmail#mail.com",
Password = "123456"
};
var mockIdataResult = new Mock<IDataResult<User>>();
mockIdataResult.Setup(i => i.Success).Returns(true);
mockIdataResult.Setup(i => i.Data).Returns(userobject);
var mockIUserService = new Mock<IUserService>();
mockIUserService.Setup(i => i.getByEmail(userinput)).Returns(mockIdataResult.Object);
var authscontroller = new AuthsController(mockIUserService.Object);
// action
IActionResult result = authscontroller.getcustomerlogin(userinput);
var okResult = result as OkObjectResult;
// assert
Assert.AreEqual(200, okResult.StatusCode);
}
}
}
This is the login function we are trying to test.
namespace WEBAPII.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthsController : ControllerBase
{
IUserService _userService;
public AuthsController(IUserService userService)
{
_userService = userService;
}
[HttpPost("login")]
public IActionResult getcustomerlogin(UserForLogin userForLogin)
{
var user = _userService.getByEmail(userForLogin);
if (user.Success)
{
if (!(user.Data.Email == userForLogin.email &&
user.Data.Password == userForLogin.password))
{
return BadRequest(user);
}
return Ok(user);
}
return BadRequest(user);
}
[HttpPost("logindadmin")]
public IActionResult adminlogin(UserForLogin userForLogin)
{
var admin = _userService.getAdmin(userForLogin);
if (admin.Success)
{
return Ok(admin);
}
return BadRequest(admin);
}
}
}
This is the IUserService interface that is set inside Authscontroller
namespace Business.Abstract
{
public interface IUserService
{
List<User> GetAll();
User GetById(int userId);
void Add(User user);
IDataResult<User> getByEmail(UserForLogin userForLogin);
IDataResult<User> getAdmin(UserForLogin userForLogin);
}
}
This is UserForLogin class that takes user information parameters.
public class UserForLogin
{
public string email { get; set; }
public string password { get; set; }
}
This is the User class that we store our information:
namespace Entities.Concrete
{
public class User : IEntity
{
public int id { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime CreatedAt { get; set; }
public string roles { get; set; }
}
}
To test your functionality with actual data from the database, you need to implement an integration test instead of a unit test.
You can create a new test database or a replica of the existing production server and populate some test data into it.
Use EF or ADO.Net for DB operations in place of macking.

Entity Framework 6.2.0 - DbContext that automatically saves which user created or updated a DbSet value

As the title says we need to store which user updated or created a specific value. Previously we only needed to save CreatedDate and UpdatedDate. This was quite easy, we had all our entities inherit from an abstract base class
called EntityBase which implemented the interface IEntity that had these values. DbContext SaveChanges was then overriden and if a specific entity implemented IEntity the values were set based on EntityState.Added or EntityState.Modified.
interface IEntity
{
DateTime CreatedDate { get; set; }
string CreatedBy { get; set; }
DateTime UpdatedDate { get; set; }
string UpdatedBy { get; set; }
}
public abstract class EntityBase<T1>: IEntity
{
public T1 Id { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual string CreatedBy { get; set; }
public virtual DateTime UpdatedDate { get; set; }
public virtual string UpdatedBy { get; set; }
}
However a new requirement is that we also need to save which user has made the changes. A simple solution would be to add the user via the constructor and not have an empty constructor. However this proves a problem when using migrations among others:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<CatalogServiceContext, Configuration>());
var migrator = new DbMigrator(new Configuration());
The target context 'CatalogService.Data.Context.CatalogServiceContext'
is not constructible. Add a default constructor or provide an
implementation of IDbContextFactory.
public class CatalogServiceContext : DbContext
{
private readonly string _currentUser;
public CatalogServiceContext(string currentUser) : base("name=CatalogContext")
{
_currentUser = currentUser;
}
public override int SaveChanges()
{
var now = DateTime.Now;
foreach (var changedEntity in ChangeTracker.Entries())
{
if (changedEntity.Entity is IEntity entity)
{
switch (changedEntity.State)
{
case EntityState.Added:
entity.CreatedDate = now;
entity.CreatedBy = _currentUser;
entity.UpdatedDate = now;
entity.UpdatedBy = _currentUser;
break;
case EntityState.Modified:
Entry(entity).Property(x => x.CreatedDate).IsModified = false;
Entry(entity).Property(x => x.CreatedBy).IsModified = false;
entity.UpdatedDate = now;
entity.UpdatedBy = _currentUser;
break;
}
}
}
return base.SaveChanges();
}
}
I tried implementing a IDbContextFactory but it has a similar problem.
The context factory type
'CatalogService.Data.Context.CatalogServiceContextFactory' does not
have a public parameterless constructor. Either add a public
parameterless constructor, create an IDbContextFactory implementation
in the context assembly, or register a context factory using
DbConfiguration.
public class CatalogServiceContextFactory : IDbContextFactory<CatalogServiceContext>
{
private readonly string _currentUser;
public CatalogServiceContextFactory(string currentUser)
{
_currentUser = currentUser;
}
public CatalogServiceContext Create()
{
return new CatalogServiceContext(_currentUser);
}
}
A workaround is two methods in EntityBase that needs to be called each time a value is updated or created. This works but the best solution is still a forced user in the constructor in my opinion. Is there anyone else with a similar demand that have solved it? I would like to solve it in the DbContext and not use a repository abstraction for each DbSet.
public virtual EntityBase<T1> SetCreateFields(string currentUser)
{
CreatedBy = currentUser;
UpdatedBy = currentUser;
return this;
}
public virtual EntityBase<T1> SetUpdateFields(string currentUser)
{
UpdatedBy = currentUser;
return this;
}
Decided to solve it like this:
public class CatalogServiceContext : DbContext
{
public ICurrentUser CurrentUser;
[Obsolete("Use CatalogServiceContextUserWrapper instead")]
public CatalogServiceContext() : base("name=CatalogContext")
{
}
...
public class CatalogServiceContextUserWrapper
{
public CatalogServiceContext Context;
public CatalogServiceContextUserWrapper(CatalogServiceContext context, ICurrentUser currentUser)
{
context.CurrentUser = currentUser;
this.Context = context;
}
}

Map Private Property

I have an Asp.Net project with Entity Framework 7 an i have a Email class with a list of attachments.
I don't want to leave that anyone add a item to my list, them i have
private List<Attachment> Resources { get; set; }
public IEnumerable<Attachment> Attachments { get; set; }
Now, I want to map to database the relationship with the property Resources instead Attachments.
Entity Framework 7 rise an Exception...
How i can do this.
Separate the this to two different model, one internal that maps to the database and another one that's available to users.
It's also the correct way of passing data between layers.
Hope it helps!
I agree with Itay.
Maybe this code example could help you.
Make entities that map to db tables.
public class EmailState
{
public int Id { get; private set; }
public List<AttachmentState> Resources { get; set; }
public static Email ToEmail(EmailState state)
{
return new Email(state);
}
}
public class AttachmentState
{
public static Attachment ToAttachment(AttachmentState state)
{
return new Attachment(state);
}
public Attachment ToAttachment()
{
return new Attachment(this);
}
}
Make classes that are available to users
public class Email
{
public Email()
{
this.State = new EmailState();
}
internal Email(EmailState state)
{
this.State = state;
}
internal EmailState State { get; set; }
public int Id { get; private set; }
public IEnumerable<Attachment> Attachments()
{
return this.State.Resources.Select(x => x.ToAttachment());
}
public void AddAttachment(Attachment attachment)
{
this.State.Resources.Add(attachment.State);
}
}
public class Attachment
{
public Attachment()
{
this.State = new AttachmentState();
}
internal Attachment(AttachmentState state)
{
this.State = state;
}
internal AttachmentState State { get; set; }
}
Define DbContext
public class EmailDbContext : DbContext
{
public DbSet<EmailState> Emails { get; set; }
public DbSet<AttachmentState> Attachments { get; set; }
}
Make repository
public interface IEmailRepository
{
void Add(Email email);
Email GetById(int emailId);
}
public class EmailRepository : IEmailRepository
{
private EmailDbContext _context;
public EmailRepository(EmailDbContext context)
{
_context = context;
}
public void Add(Email email)
{
_context.Emails.Add(email.State);
}
public Email GetById(int emailId)
{
EmailState emailState = _context.Emails.Single(x => x.Id == emailId);
return new Email(emailState);
}
}
Use it like this
using (var context = new EmailDbContext())
{
IEmailRepository repository = new EmailRepository(context);
var email = new Email();
repository.Add(email);
context.SaveChanges();
var emailFoundById = repository.GetById(email.Id);
}

Extending ASP.NET Identity Roles: IdentityRole is not part of the model for the current context

I'm trying to use the new ASP.NET Identity in my MVC5 application, specifically I'm trying to integrate ASP.NET Identity into an existing database. I've already read the questions/answers on SO pertaining to DB First and ASP.NET Identity, and having followed all the recommendations I still can't add roles to my database, although I have no problems adding users. Here's my code:
var context = new PayrollDBEntities();
var roleManager = new RoleManager<AspNetRole>(new RoleStore<AspNetRole>(context));
bool roleExists = roleManager.RoleExists(roleDto.Name);
if (roleExists){
return false;
}
var role = new AspNetRole(roleDto.Name){
Name = roleDto.Name,
};
IdentityResult result = roleManager.Create(role);//Getting exception here
At the last line of code I get an exception of type 'System.InvalidOperationException': The entity type IdentityRole is not part of the model for the current context.
Here is my context:
public partial class PayrollDBEntities : IdentityDbContext
{
public PayrollDBEntities()
: base("name=PayrollDBEntities")
{
}
public virtual DbSet<AspNetRole> AspNetRoles { get; set; }
public virtual DbSet<AspNetUserClaim> AspNetUserClaims { get; set; }
public virtual DbSet<AspNetUserLogin> AspNetUserLogins { get; set; }
public virtual DbSet<AspNetUser> AspNetUsers { get; set; }
......
}
My AspNetUser and AspNetRole classes derive from IdentityUser and IdentityRole respectively, but I'm still getting that exception. Here is my database diagram:
Any help would be greatly appreciated.
You have to specify during the creation of User Store that AspNetRole is used instead of IdentityRole. You can achieve this by using the UserStore class with 6 type parameters:
new UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>(new PayrollDBEntities());
This indicates changes at User Manager creation as well. Here is a simplified example about the creation of needed instances:
public class AspNetUser : IdentityUser { /*customization*/ }
public class AspNetRole : IdentityRole { /*customization*/ }
public class PayrollDBEntities : IdentityDbContext //or : IdentityDbContext <AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
{
}
public class Factory
{
public IdentityDbContext DbContext
{
get
{
return new PayrollDBEntities();
}
}
public UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim> UserStore
{
get
{
return new UserStore<AspNetUser, AspNetRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>(DbContext);
}
}
public UserManager<AspNetUser, string> UserManager
{
get
{
return new UserManager<AspNetUser, string>(UserStore);
}
}
public RoleStore<AspNetRole> RoleStore
{
get
{
return new RoleStore<AspNetRole>(DbContext);
}
}
public RoleManager<AspNetRole> RoleManager
{
get
{
return new RoleManager<AspNetRole>(RoleStore);
}
}
}
After a few days of trying to get this to work in a clean manner, I've come to the conclusion that if you're using Database first and want to integrate ASP.NET Identity into your app, by far the easiest and cleanest solution is to create your own membership provider by overriding ASP.NET Identity. It's actually pretty easy, so far I've implemented UserStore and RoleStore to my liking. I've added columns/relations specific to my domain in my database, and whenever I create a user or a role, I take care of my database commits by adding the required relations. My UserStore implementation is quite similar to this. My RoleStore implementation is something like this:
public class ApplicationRoleStore : IRoleStore<ApplicationRoleDTO>
{
private PayrollDBEntities _context;
public ApplicationRoleStore() { }
public ApplicationRoleStore(PayrollDBEntities database)
{
_context = database;
}
public Task CreateAsync(ApplicationRoleDTO role)
{
if (role == null)
{
throw new ArgumentNullException("RoleIsRequired");
}
var roleEntity = ConvertApplicationRoleDTOToAspNetRole(role);
_context.AspNetRoles.Add(roleEntity);
return _context.SaveChangesAsync();
}
public Task DeleteAsync(ApplicationRoleDTO role)
{
var roleEntity = _context.AspNetRoles.FirstOrDefault(x => x.Id == role.Id);
if (roleEntity == null) throw new InvalidOperationException("No such role exists!");
_context.AspNetRoles.Remove(roleEntity);
return _context.SaveChangesAsync();
}
public Task<ApplicationRoleDTO> FindByIdAsync(string roleId)
{
var role = _context.AspNetRoles.FirstOrDefault(x => x.Id == roleId);
var result = role == null
? null
: ConvertAspNetRoleToApplicationRoleDTO(role);
return Task.FromResult(result);
}
public Task<ApplicationRoleDTO> FindByNameAsync(string roleName)
{
var role = _context.AspNetRoles.FirstOrDefault(x => x.Name == roleName);
var result = role == null
? null
: ConvertAspNetRoleToApplicationRoleDTO(role);
return Task.FromResult(result);
}
public Task UpdateAsync(ApplicationRoleDTO role)
{
return _context.SaveChangesAsync();
}
public void Dispose()
{
_context.Dispose();
}
private ApplicationRoleDTO ConvertAspNetRoleToApplicationRoleDTO(AspNetRole aspRole)
{
return new ApplicationRoleDTO{
Id = aspRole.Id,
EnterpriseId = aspRole.EnterpriseId,
Name = aspRole.Name
};
}
private AspNetRole ConvertApplicationRoleDTOToAspNetRole(ApplicationRoleDTO appRole)
{
return new AspNetRole{
Id = appRole.Id,
EnterpriseId = appRole.EnterpriseId,
Name = appRole.Name,
};
}
}
And my ApplicationRoleDTO:
public class ApplicationRoleDTO : IRole
{
public ApplicationRoleDTO()
{
Id = Guid.NewGuid().ToString();
}
public ApplicationRoleDTO(string roleName)
: this()
{
Name = roleName;
}
public string Id { get; set; }
public string Name { get; set; }
public Guid EnterpriseId { get; set; }
}
I also found these 2 articles pretty helpful:
Overview of Custom Storage Providers for ASP.NET Identity
Implementing a Custom MySQL ASP.NET Identity Storage Provider
I'll explain here with the code exampels :).
The trick is, they are already in the IdentityDbContext (AspNetRoles, AspNetUserClaims, AspNetUsers, ....)
In the IdentityModel you will see ApplicationUser is empty at the top. If you want to customize these users or roles, just add properties here and then update your database via the console
Example of my context
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public DbSet<Request> Requests { get; set; }
public DbSet<Reservation> Reservations { get; set; }
public DbSet<PriceType> PriceTypes { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Price> Prices { get; set; }
public DbSet<GuestbookPost> Posts { get; set; }
public DbSet<Count> Counts { get; set; }
public DbSet<Invoice> Invoices { get; set; }
public DbSet<InvoiceLine> InvoiceLines { get; set; }
...
}
So no application user is defined here, but I did add more properties to it, example:
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string GroupName { get; set; }
public string Email { get; set; }
[StringLength(15)]
public string Phone { get; set; }
public string Remark { get; set; }
public DateTime? BirthDate { get; set; }
public DateTime ValidFrom { get; set; }
public DateTime ValidUntil { get; set; }
public string Street { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public virtual ICollection<Request> Requests { get; set; }
}
I know this is an old question, but just in case someone else is having a hard time adding roles/users when they modified asp identity to use numeric primary keys (int/long) instead of the default string for the Identity Roles, so if you have changed the IdentityUserRole in IdentityModels.cs to something like this:
public class Role : IdentityRole<long, UserRole>
{
public Role() { }
public Role(string name) { Name = name; }
}
You have to use the class Role instead of the default IdentityRole when constructing the RoleManager, so your code should be like this:
public static void RegisterUserRoles()
{
ApplicationDbContext context = new ApplicationDbContext();
var RoleManager = new RoleManager<Role, long>(new RoleStore(context));
if (!RoleManager.RoleExists("Administrador"))
{
var adminRole = new Role {
Name = "Administrador",
};
RoleManager.Create(adminRole);
}
}
So this should populate your database properly, I think all experienced ASP programmers already know this, but for others this could take some time to figure out.
I solved with a different way.
First I splited in two different Projects and Contexts.
My project that Handle the Identity has this context:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>, IDisposable
{
public ApplicationDbContext()
: base("DefaultConnection", throwIfV1Schema: false)
{
}
public static ApplicationDbContext Create()
{
return new ApplicationDbContext();
}
}
This is my ApplicationUser:
public class ApplicationUser : IdentityUser
{
//Put here the extra properties that Identity does not handle
[Required]
[MaxLength(150)]
public string Nome { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
And my ApplicationUserManager looks like this:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
//Setting validator to user name
UserValidator = new UserValidator<ApplicationUser>(this)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
//Validation Logic and Password complexity
PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = false,
RequireDigit = false,
RequireLowercase = false,
RequireUppercase = false,
};
//Lockout
UserLockoutEnabledByDefault = true;
DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
MaxFailedAccessAttemptsBeforeLockout = 5;
// Providers de Two Factor Autentication
RegisterTwoFactorProvider("Código via SMS", new PhoneNumberTokenProvider<ApplicationUser>
{
MessageFormat = "Seu código de segurança é: {0}"
});
RegisterTwoFactorProvider("Código via E-mail", new EmailTokenProvider<ApplicationUser>
{
Subject = "Código de Segurança",
BodyFormat = "Seu código de segurança é: {0}"
});
//Email service
EmailService = new EmailService();
// Definindo a classe de serviço de SMS
SmsService = new SmsService();
var provider = new DpapiDataProtectionProvider("Braian");
var dataProtector = provider.Create("ASP.NET Identity");
UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtector);
}
}
I hope that this helps someone.
This solution was from this article:
Eduardo Pires - But it is in Portuguese
I fixed this issue by changing the web.config DefaultConnection connectionString property so it points to the new SQLServer database

UrlHelper in .net web api builder

I'm building a web api and I have for example this controller,
[RoutePrefix("api/v{version}/profile")]
public class ProfileController : BaseController
{
private readonly IPersonService _personService;
private readonly IPersonDtoBuilder _builder;
public ProfileController(IPersonService personService
IPersonDtoBuilder builder)
{
_personService = personService;
_builder = builder;
}
[HttpGet]
[Route("")]
public IHttpActionResult Get()
{
var person = _personService.GetPerson(UserId);
if (person == null)
return NotFound();
var model = _builder.Build(person);
return Ok(model);
}
Being the IPersonDtoBuilder and PersonDtoBuilder
public interface IPersonDtoBuilder
{
PersonDto Build(UrlHelper urlHelper, Person person);
}
public class PersonDtoBuilder : IPersonDtoBuilder
{
public PersonDto Build(UrlHelper urlHelper, Person person)
{
var model = new PersonDto
{
Links = new List<Link>
{
new Link
{
Rel = "update",
Href = urlHelper.Link("UpdateProfile", null),
Title = "Update"
}
},
FirstName = person.FirstName,
MiddleName = person.MiddleName,
LastName = person.Surname
};
return model;
}
}
}
The personDto is a model that derives from Resources just for the sake of having a property Links in all of the derived classes.
[DataContract]
public class PersonDto : Resource
{
[DataMember(Name = "firstName")]
public string FirstName { get; set; }
[DataMember(Name = "middleName")]
public string MiddleName { get; set; }
[DataMember(Name = "lastName")]
public string LastName { get; set; }
}
[DataContract]
public abstract class Resource
{
[DataMember(Name = "_links")]
public IEnumerable<Link> Links { get; set; }
}
My problem is when I'm trying to construct this links in the builder, as I don't have access to the Url.Link method.
I was trying to pass it as a parameter
PersonDto Build(UrlHelper urlHelper, Person person)
and somehow initialize it in the basecontroller
But I'm still not able to initialize it properly. How should I do it?
Thanks
Edit: After reading Silvermind comment I review my basecontroller and figured out how it works
public abstract class BaseController : ApiController
{
UrlHelper _urlHelper;
protected UrlHelper TheUrlHelper
{
get
{
if (_urlHelper == null)
{
_urlHelper = new UrlHelper(this.Request);
}
return _urlHelper;
}
}
public long UserId { get; set; }
}
Not sure if it's the cleanest way passing the helper to the build methods of all the classes but it seems to fix this issue

Categories

Resources