In their website, Xamarin appears as one of their clients, but I'm unable to install the package Breeze.Sharp, which is also tagged with Xamarin.
It does install into the PCL project, but for it to work I need to install it into all of the platform projects. When I try to do so, I get the following errors:
iOS/Android:
Could not install package 'Microsoft.AspNet.WebApi.Client 5.2.3'. You are trying to install this package into a project that targets 'Xamarin.iOS,Version=v1.0', but the package does not contain any assembly references or content files that are compatible with that framework. For more information, contact the package author.
UWP:
Package Breeze.Sharp 0.6.0.9 is not compatible with uap10.0 (UAP,Version=v10.0) / win10-x86-aot. Package Breeze.Sharp 0.6.0.9 supports: net (.NETFramework,Version=v0.0)
One or more packages are incompatible with UAP,Version=v10.0 (win10-x86-aot).
I resolve in this way.
1. Create New Portable Class Library
2. Add BreezeSharp as reference of the new Portable Class Library(Nuget)
3. Add the New Class Library as reference of your specific platform projects(Android, iOS). In my case only Android.
4. In Portable Library. Create a static class ex: Configs.
5. Android project -> MainActivity.OnCreate
Configs.ModelAssembly = typeof(Customer).Assembly;
Implement Breeze's DataService in the Portable Class Library.
ex:
public abstract class BaseDataService<T> where T : BaseEntity
{
public static string Metadata { get; protected set; }
public string EntityName { get; protected set; }
public string EntityResourceName { get; protected set; }
public EntityManager EntityManager { get; set; }
public string DefaultTargetMethod { get; protected set; }
static BaseDataService()
{
Constants = ConstantsFactory.Get;
try
{
var metadata = GetMetadata();
metadata.Wait();
Metadata = metadata.Result;
}
catch (Exception ex)
{
var b = 0;
}
}
public BaseDataService(string resourceName, string targetMethod = null)
{
var modelType = typeof(Customer);
Configuration.Instance.ProbeAssemblies(ConstantsFactory.BusinessAssembly);
try
{
this.EntityName = typeof(T).FullName;
this.EntityResourceName = resourceName;
this.DefaultTargetMethod = (string.IsNullOrWhiteSpace(targetMethod) ? "GetAll" : targetMethod);
var dataService = new DataService($"{ConstantsFactory.Get.BreezeHostUrl}{this.EntityResourceName}", new CustomHttpClient());
dataService.HasServerMetadata = false;
var metadataStore = new MetadataStore();
var namingConvention = NamingConvention.CamelCaseProperties; /*metadataStore.NamingConvention;*/ /*NamingConvention.Default;*/// new NamingConvention()
namingConvention = namingConvention.WithClientServerNamespaceMapping(
new Dictionary<string, string> { { "Business.DomainModels", "DomainModel.Models" } }
);
metadataStore.NamingConvention = namingConvention;
metadataStore.ImportMetadata(Metadata, true);
this.EntityManager = new EntityManager(dataService, metadataStore);
this.EntityManager.MetadataStore.AllowedMetadataMismatchTypes = MetadataMismatchTypes.AllAllowable;
// Attach an anonymous handler to the MetadataMismatch event
this.EntityManager.MetadataStore.MetadataMismatch += (s, e) =>
{
// Log the mismatch
var message = string.Format("{0} : Type = {1}, Property = {2}, Allow = {3}",
e.MetadataMismatchType, e.StructuralTypeName, e.PropertyName, e.Allow);
// Disallow missing navigation properties on the TodoItem entity type
if (e.MetadataMismatchType == MetadataMismatchTypes.MissingCLRNavigationProperty &&
e.StructuralTypeName.StartsWith("TodoItem"))
{
e.Allow = false;
}
};
}
catch (Exception ex)
{
var b = 0;
}
}
public async Task<List<T>> GetAll(string targetMethod = null)
{
var internalTargetMethod = (string.IsNullOrWhiteSpace(targetMethod) ? this.DefaultTargetMethod : targetMethod);
var query = new EntityQuery<T>(internalTargetMethod);
var qr = await this.EntityManager.ExecuteQuery(query);
var result = qr.ToList();
return result;
}
public void Delete(T entity)
{
entity.EntityAspect.Delete();
}
private static async Task<string> GetMetadata()
{
var client = new HttpClient();
var metadata = await client.GetStringAsync(ConstantsFactory.Get.MetadataUrl).ConfigureAwait(false);
var ret = JsonConvert.DeserializeObject<MetadataModel>(metadata);
return ret.metadata;
}
}
Create CustomerService if you need as
public class CustomerDataService : BaseDataService<Customer>
{
public CustomerDataService(IConstants constants) : base("Customers")
{
}
}
In an email I got from IdeaBlade, the inventor of Breeze#, he said that while the Breeze# is not dead, it has drown much less community attention compared to BreezeJS, thus they prefer to invest their resources in the JS library.
For now, I'm just gonna use TrackableEntities by #AnthonySneed. Although not as comprehensive as Breeze#, does part of the job nicely.
Related
I am developing asp.net core 3.1 GraphQL based APIs. I used the below reference article to setup automatic DI configuration in the API layer and have used the nuget package : NetCore.AutoRegisterDi
https://www.thereformedprogrammer.net/asp-net-core-fast-and-automatic-dependency-injection-setup/
Here goes the code details:
Code:
Startup.cs:
public virtual void ConfigureServices(IServiceCollection services) => services
.AddGraphQLResolvers()
.AddProjectRepositories();
ProjectServiceCollectionExtensions.cs
public static class ProjectServiceCollectionExtensions
{
public static IServiceCollection AddProjectRepositories(this IServiceCollection services) =>
services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
.Where(c => c.Name.EndsWith("Persistence"))
.AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
public static IServiceCollection AddGraphQLResolvers(this IServiceCollection services) =>
services
.AddScoped<ICountriesResolver, CountriesResolver>()
.AddScoped<ICountryGroupsResolver, CountryGroupsResolver>()
.AddScoped<IDisclaimerResolver, DisclaimerResolver>();
}
Here in the above CommonService is part of the Service Layer that ends with Persistence.
CountriesResolver.cs
public class CountriesResolver : Resolver, ICountriesResolver
{
private readonly ICountryService _countryService;
private readonly IHttpContextAccessor _accessor;
private readonly IDataLoaderContextAccessor _dataLoaderContextAccessor;
public CountriesResolver(ICountryService countryService, IHttpContextAccessor accessor, IDataLoaderContextAccessor dataLoaderContextAccessor)
{
_countryService = countryService ?? throw new ArgumentNullException(nameof(countryService));
_accessor = accessor;
_dataLoaderContextAccessor = dataLoaderContextAccessor;
}
public void Resolve(GraphQLQuery graphQLQuery)
{
var language = _accessor.HttpContext.Items["language"] as LanguageDTO;
graphQLQuery.FieldAsync<ResponseGraphType<CountryResultType>>("countriesresponse", arguments: new QueryArguments(new QueryArgument<IdGraphType>{Name = "pageNo", Description = "page number"}, new QueryArgument<IdGraphType>{Name = "pageSize", Description = "page size"}), resolve: async context =>
{
var pageNo = context.GetArgument<int>("pageNo") == 0 ? 1 : context.GetArgument<int>("pageNo");
var pageSize = context.GetArgument<int>("pageSize") == 0 ? 100 : context.GetArgument<int>("pageSize");
if (language != null)
{
var loader = _dataLoaderContextAccessor.Context.GetOrAddLoader("GetAllCountries", () => _countryService.GetAllCountriesAsync(language, pageNo, pageSize));
var list = await context.TryAsyncResolve(async c => await loader.LoadAsync());
return Response(list);
}
return null;
}
, description: "All Countries data");
}
}
ICommonService.cs
using Author.Query.Persistence.DTO;
using System.Threading.Tasks;
namespace Author.Query.Persistence.Interfaces
{
public interface ICommonService
{
LanguageDTO GetLanguageFromLocale(string locale);
Task<LanguageDTO> GetLanguageFromLocaleAsync(string locale);
}
}
CommonService.cs
namespace Author.Query.Persistence
{
public class CommonService : ICommonService
{
private readonly AppDbContext _dbContext;
private readonly IOptions<AppSettings> _appSettings;
private readonly IMapper _mapper;
private readonly ICacheService<Languages, LanguageDTO> _cacheService;
public CommonService(AppDbContext dbContext, IOptions<AppSettings> appSettings, IMapper mapper, ICacheService<Languages, LanguageDTO> cacheService)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_appSettings = appSettings;
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
}
//public Languages GetLanguageFromLocale(string locale)
public LanguageDTO GetLanguageFromLocale(string locale)
{
return GetLanguagesByFilter(GetFilterValues(locale, true).ToArray());
}
}
}
ICountryService.cs
namespace Author.Query.Persistence.Interfaces
{
public interface ICountryService
{
Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize);
Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId);
}
}
CountryService.cs
namespace Author.Query.Persistence
{
public class CountryService : ICountryService
{
private readonly AppDbContext _dbContext;
private readonly IOptions<AppSettings> _appSettings;
private readonly ICacheService<Images, ImageDTO> _cacheService;
public CountryService(TaxathandDbContext dbContext, IOptions<AppSettings> appSettings, ICacheService<Images, ImageDTO> cacheService)
{
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService));
_appSettings = appSettings;
}
public async Task<CountryResult> GetAllCountriesAsync(LanguageDTO language, int pageNo, int pageSize)
{
var localeLangId = language.LanguageId;
var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
// By default pick the localLanguage value
var countries = await GetAllCountriesDataAsync(localeLangId, pageNo, pageSize);
// If localLanguage data is not available then pull the data based on default language
if (countries.Countries.Count == 0)
{
countries = await GetAllCountriesDataAsync(dftLanguageId, pageNo, pageSize);
}
return countries;
}
public async Task<CountryDTO> GetCountryAsync(LanguageDTO language, int countryId)
{
var localeLangId = language.LanguageId;
var dftLanguageId = int.Parse(_appSettings.Value.DefaultLanguageId);
//var country = new CountryDTO();
// By default pick the localLanguage value
var country = await GetCountryDetailsAsync(countryId, localeLangId);
// If localLanguage data is not available then pull the data based on default language
if (country == null)
{
country = await GetCountryDetailsAsync(countryId, dftLanguageId);
}
return country;
}
private async Task<CountryDTO> GetCountryDetailsAsync(int countryId, int languageId)
{
var images = await _cacheService.GetAllAsync("imagesCacheKey");
var country = await _dbContext.Countries.AsNoTracking().FirstOrDefaultAsync(c => c.CountryId.Equals(countryId) && c.IsPublished.Equals(true) && c.LanguageId.Equals(languageId));
if (country == null)
{
return null;
}
var countryDTO = new CountryDTO{Uuid = country.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(country.SVGImageId)).FilePath, DisplayName = country.DisplayName, DisplayNameShort = country.DisplayName, Name = Helper.ReplaceChars(country.DisplayName), Path = Helper.ReplaceChars(country.DisplayName), CompleteResponse = true};
return countryDTO;
}
private async Task<CountryResult> GetAllCountriesDataAsync(int languageId, int pageNo, int pageSize)
{
var countryList = new CountryResult();
var images = await _cacheService.GetAllAsync("imagesCacheKey");
var countries = await _dbContext.Countries.Where(cc => cc.IsPublished.Equals(true) && cc.LanguageId.Equals(languageId)).Select(c => new
{
c.CountryId, c.DisplayName, c.PNGImageId, c.SVGImageId
}
).OrderByDescending(c => c.CountryId).Skip((pageNo - 1) * pageSize).Take(pageSize).AsNoTracking().ToListAsync();
if (countries.Count == 0)
{
return null;
}
countryList.Countries.AddRange(countries.Select(co => new CountryDTO{Uuid = co.CountryId, PNGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.PNGImageId)).FilePath, SVGImagePath = images.FirstOrDefault(im => im.ImageId.Equals(co.SVGImageId)).FilePath, DisplayName = co.DisplayName, DisplayNameShort = co.DisplayName, Name = Helper.ReplaceChars(co.DisplayName), Path = Helper.ReplaceChars(co.DisplayName), CompleteResponse = true}));
return countryList;
}
}
}
Error:
System.AggregateException
HResult=0x80131500
Message=Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountriesResolver':
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.) (Error while validating the service descriptor 'ServiceType:
Author.Query.New.API.GraphQL.Resolvers.ICountryGroupsResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryGroupService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.CountryGroupsResolver'.) (Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.IDisclaimerResolver Lifetime: Scoped ImplementationType: Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver': Unable to resolve service for type 'Author.Query.Persistence.Interfaces.IDisclaimerService' while attempting to activate 'Author.Query.New.API.GraphQL.Resolvers.DisclaimerResolver'.)
Source=Microsoft.Extensions.DependencyInjection
StackTrace:
at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder)
at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
at Microsoft.Extensions.Hosting.HostBuilder.Build()
at Author.Query.New.API.Program.Main(String[] args) in /src/QueryStack/Author.Query.New.API/Program.cs:line 15
Inner Exception 1:
InvalidOperationException: Error while validating the service descriptor 'ServiceType: Author.Query.New.API.GraphQL.Resolvers.ICountriesResolver Lifetime: Scoped ImplementationType:
Author.Query.New.API.GraphQL.Resolvers.CountriesResolver':
Unable to resolve service for type 'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.
Inner Exception 2:
InvalidOperationException: Unable to resolve service for type
'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'.
Can anyone help me to know how to fix this issue?
The error says that :
Unable to resolve service for type
'Author.Query.Persistence.Interfaces.ICountryService' while attempting to activate
'Author.Query.New.API.GraphQL.Resolvers.CountriesResolver'
ICountryService is never registered. AddGraphQLResolvers registers only ICountriesResolver, ICountryGroupsResolver and IDisclaimerResolver.
The method AddProjectRepositories only registers classes whose name ends in Persistence that appear in the same namespace as CommonService. It never registers CommonService itself.
The service must be registered, eg with :
services.AddScoped<ICommonService, CommonService>();
Your method AddProjectRepositories() does not register CountryService, because the Where filter in RegisterAssemblyPublicNonGenericClasses() looks at classes (Type), not at namespaces.
So perhaps you can change your filter:
services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(typeof(CommonService)))
.Where(c => c.Name.EndsWith("Service")) // <-- Change "Persistence" to "Service"
.AsPublicImplementedInterfaces(ServiceLifetime.Scoped);
That should register all classes that end with "Service" in the Assembly that contains CommonService.
For me it was a simple mistake. I had a class with the same name in another namespace and referenced the wrong class/forgot to delete the duplicate class and add the correct one to my startup.
I had a service called UserService in myapp.Utils and another in myapp.Services. I was referencing myapp.Utils when I meant to delete that one and only use myapp.Services. I was incorrectly injecting the one in myapp.Utils when my controllers were set up to use the one in myapp.Services.
I'm implementing an application with Breezesharp. I ran into a issue when insert the entity in the EntityManager. The error is:
There are no KeyProperties yet defined on EntityType: 'TransportReceipt:#Business.DomainModels'
I already faced this error with my first entity type "Customer" and implement a mismatching approach as suggested here. In that case I made the get operation against my WebApi with success. But now I'm creating the TransportReceipt entity inside my application.
Mapping mismatch fix
public static class ExtendMap
{
private static bool? executed;
public static void Execute(MetadataStore metadataStore) {
if (ExtendMap.executed == true)
{
return;
}
var customerBuilder = new EntityTypeBuilder<Customer>(metadataStore);
customerBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptBuilder = new EntityTypeBuilder<TransportReceipt>(metadataStore);
transportReceiptBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptAttachmentBuilder = new EntityTypeBuilder<TransportReceiptAttachment>(metadataStore);
transportReceiptAttachmentBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var uploadedFileBuilder = new EntityTypeBuilder<UploadedFile>(metadataStore);
uploadedFileBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
ExtendMap.executed = true;
}
}
My base dataservice core code
public abstract class SimpleBaseDataService
{
public static string Metadata { get; protected set; }
public static MetadataStore MetadataStore { get; protected set; }
public string EntityName { get; protected set; }
public string EntityResourceName { get; protected set; }
public EntityManager EntityManager { get; set; }
public string DefaultTargetMethod { get; protected set; }
static SimpleBaseDataService()
{
try
{
var metadata = GetMetadata();
metadata.Wait();
Metadata = metadata.Result;
MetadataStore = BuildMetadataStore();
}
catch (Exception ex)
{
var b = 0;
}
}
public SimpleBaseDataService(Type entityType, string resourceName, string targetMethod = null)
{
var modelType = typeof(Customer);
Configuration.Instance.ProbeAssemblies(ConstantsFactory.BusinessAssembly);
try
{
this.EntityName = entityType.FullName;
this.EntityResourceName = resourceName;
this.DefaultTargetMethod = (string.IsNullOrWhiteSpace(targetMethod) ? "GetAllMobile" : targetMethod);
var dataService = new DataService($"{ConstantsFactory.Get.BreezeHostUrl}{this.EntityResourceName}", new CustomHttpClient());
dataService.HasServerMetadata = false;
this.EntityManager = new EntityManager(dataService, SimpleBaseDataService.MetadataStore);
this.EntityManager.MetadataStore.AllowedMetadataMismatchTypes = MetadataMismatchTypes.AllAllowable;
// Attach an anonymous handler to the MetadataMismatch event
this.EntityManager.MetadataStore.MetadataMismatch += (s, e) =>
{
// Log the mismatch
var message = string.Format("{0} : Type = {1}, Property = {2}, Allow = {3}",
e.MetadataMismatchType, e.StructuralTypeName, e.PropertyName, e.Allow);
// Disallow missing navigation properties on the TodoItem entity type
if (e.MetadataMismatchType == MetadataMismatchTypes.MissingCLRNavigationProperty &&
e.StructuralTypeName.StartsWith("TodoItem"))
{
e.Allow = false;
}
};
}
catch (Exception ex)
{
var b = 0;
}
}
}
This is who I'm trying to add the new entity
//DataService snippet
public void AttachEntity(T entity)
{
this.EntityManager.AttachEntity(entity, EntityState.Added);
}
//Business
this.TransportReceipt = new TransportReceipt { id = Guid.NewGuid(), date = DateTime.Now, customerId = Customer.id/*, customer = this.Customer*/ };
this.Attachments = new List<TransportReceiptAttachment>();
this.TransportReceipt.attachments = this.Attachments;
TransportReceiptDataService.AttachEntity(this.TransportReceipt);
When I try to add add the entity to the EntityManager, I can see the custom mapping for all my entity classes.
So my question is what I'm doing wrong.
Ok. That was weird.
I changed the mapping for a new fake int property and works. I'll test the entire save flow soon and I'll share the result here.
Update
I moved on and start removing Breezesharp. The Breezesharp project is no up-to-date and doesn't have good integration with Xamarin. I'll appreciate any comment with your experience.
I have an intranet application that uses the Windows username and passes that to a procedure to return data.
I'm using dependency injection, but I don't believe I have the method to get the username separated properly.
I'm trying to keep this secure by not passing in the username as a parameter, but I also want to be able to impersonate (or bypass my GetWindowsUser() method) and send in another username so I can test results for other users.
One idea I had for this was to set a session variable in another page with another (impersonated) username, then check if that session variable exists first before grabbing the actual user name, but I couldn't figure out how to access the session variable in the repository.
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
return _dropDownDataRepository.MyList();
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
}
REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
public HttpResponseMessage MyList()
{
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
//Grab the windowsUser from the method
var windowsUser = utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
}
INTERFACE
public interface IDropDownDataRepository : IDisposable
{
HttpResponseMessage MyList();
}
UTILITY CLASS
public class Utility
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UPDATE 1
In addition to what Nikolai and Brendt posted below, the following is also needed to allow Web Api controllers work with the session state.
Accessing Session Using ASP.NET Web API
Abstract the Utility class and inject it into the repository.
Then you can stub or mock for testing.
public interface IUtility
{
string GetWindowsUser();
}
public class TestUtility : IUtility
{
public string GetWindowsUser()
{
return "TestUser";
}
}
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private IUtility _utility;
public DropDownDataRepository(IUtility utility)
{
_utility = utility;
}
}
EDIT
Also the repository should not return an HTTPResponseMessage type it should just return a List<T> of the domain model you're accessing.
i.e.
public List<Model> MyList()
{
//Grab the windowsUser from the method
var windowsUser = _utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
return sourceQuery
}
Then move the JSON portion to the controller.
One idea I had for this was to set a session variable in another page
with another (impersonated) username, then check if that session
variable exists first before grabbing the actual user name, but I
couldn't figure out how to access the session variable in the
repository.
Potentially, if you add in a dependency to session, you need to isolate it, e.g.
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private ISession session;
public DropDownDataRepository(ISession session)
{
this.session = session;
}
public HttpResponseMessage MyList()
{
var myUserName = this.session.UserName;
// ... etc
With ISession being something like:
public interface ISession
{
string UserName { get; }
}
Implemented as:
public class MySession : ISession
{
public string UserName
{
get
{
// potentially do some validation and return a sensible default if not present in session
return HttpContext.Current.Session["UserName"].ToString();
}
}
}
Of course there is the potential to decouple this MySession class from HttpContext if desired.
With regards to this:
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
Yes, anytime you create a new object you are tightly coupling them together, which will give you issues, for example, if you try to unit test it in isolation.
In this instance you could extract an IUtility interface from Utility:
public class Utility : IUtility
{
string GetWindowsUser();
}
Then:
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private IUtility utility;
public DropDownDataRepository(IUtility utility)
{
this.utility = utility;
// .... etc
Then you have removed the depenedency between Utility and DropDownDataRepository, and can substitute in another type or mock with ease.
I got a lot of help from Nikolai and Brent and got most of the way there with their posted answers, but ended up figuring out the complete answer on my own. The problems I was having were related to not being able to access session variables in a WebAPI. So, I'm sure there are cleaner solutions to this, but I definitely improved what I had and came up with the following code, which works.
This answer was needed to allow access to the session variable in Web Api - Accessing Session Using ASP.NET Web API
GLOBAL.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
UnityConfig.RegisterComponents();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
//Added to allow use of session state in Web API
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
//Added to allow use of session state in Web API
private bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(WebApiConfig.UrlPrefixRelative);
}
protected void Session_Start(Object sender, EventArgs e)
{
//Default set the session variable to none
Session["_impersonatedUser"] = "none";
}
protected void Session_End(Object sender, EventArgs e)
{
//Reset the session variable to blank
Session["_impersonatedUser"] = "";
}
}
UNITY.config
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IDropDownDataRepository, DropDownDataRepository>();
container.RegisterType<IUtilityRepository, UtilityRepository>();
container.RegisterType<ISessionRepository, SessionRepository>();
//MVC5
//Unity.MVC5 NuGet Package
DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));
//WEB API
//Unity.WebApi NuGet Package
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
}
}
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
var sourceQuery = _dropDownDataRepository.MyList();
//JSON stuff moved to controller
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
protected override void Dispose(bool disposing)
{
_dropDownDataRepository.Dispose();
base.Dispose(disposing);
}
}
DROPDOWNDATA REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
private IUtilityRepository _utilityRepository;
private ISessionRepository _sessionRepository;
//Dependency Injection of Utility and Session
public DropDownDataRepository(IUtilityRepository utilityRepository, ISessionRepository sessionRepository)
{
_utilityRepository = utilityRepository;
_sessionRepository = sessionRepository;
}
//Changed to a list here
public List<MyProcedure> MyList()
{
string windowsUser;
//Check the session variable to see if a user is being impersonated
string impersonatedUser = _sessionRepository.ImpersonatedUser;
//Grab the windowsUser from the Utility Repository
windowsUser = _utilityRepository.GetWindowsUser();
if (impersonatedUser != "none")
{
windowsUser = impersonatedUser;
}
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.MyProcedure(windowsUser)
select p).ToList();
return sourceQuery;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
DROPDOWNDATA INTERFACE
public interface IDropDownDataRepository : IDisposable
{
//Changed to list here
List<MyProcedure> MyList();
}
UTILITY REPOSITORY
public class UtilityRepository : IUtilityRepository
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UTILITY INTERFACE
public interface IUtilityRepository
{
string GetWindowsUser();
}
SESSION REPOSITORY
public class SessionRepository : ISessionRepository
{
public string ImpersonatedUser
{
get
{
return HttpContext.Current.Session["_impersonatedUser"].ToString();
}
}
}
SESSION INTERFACE
public interface ISessionRepository
{
string ImpersonatedUser { get; }
}
The implementation for authorization state in the source indicates that the authorization state should be serializable, but when I try to serialize the authorization state to store in my authentication ticket, I get an error saying that the implementation needs a parameterless constructor.
Snipped from the referenced source:
[Serializable]
public class AuthorizationState : IAuthorizationState {
...
public AuthorizationState(IEnumerable<string> scopes = null) {
How I'm getting the state:
var Authorization = Client.ProcessUserAuthorization();
How I'm trying to serialize the state:
public string Serialize(IAuthorizationState objectInstance)
{
var serializer = new XmlSerializer(objectInstance.GetType());
var sb = new StringBuilder();
using (TextWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, objectInstance);
}
return sb.ToString();
}
I ran a quick console app
class Program
{
static void Main(string[] args)
{
var can = new CanSerialize();
var cant = new CantSerialize();
var ser1 = can.Serialize();
var ser2 = cant.Serialize();
}
}
[Serializable]
public class CantSerialize
{
public int id { get; set; }
public CantSerialize(int ID = 1)
{
id = ID;
}
public string Serialize()
{
var serializer = new XmlSerializer(this.GetType());
var sb = new StringBuilder();
using (TextWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, this);
}
return sb.ToString();
}
}
[Serializable]
public class CanSerialize
{
public int id { get; set; }
private CanSerialize()
{
id = 1;
}
public CanSerialize(int ID = 1)
{
id = ID;
}
public string Serialize()
{
var serializer = new XmlSerializer(this.GetType());
var sb = new StringBuilder();
using (TextWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, this);
}
return sb.ToString();
}
}
Can the implementation have a default parameterless constructor? As the sample shows, it can be private if there is concern about having a public ctor.
In the meantime, I'm going to create a data transfer object to ferry the information back and forth between my authorization token and the state.
EDIT:
I've found that the JSON.net conversion works well:
var a = JsonConvert.SerializeObject(Authorization);
var b = JsonConvert.DeserializeObject(a, typeof(AuthorizationState));
I've found that the JSON.net conversion works well:
var a = JsonConvert.SerializeObject(Authorization);
var b = JsonConvert.DeserializeObject(a, typeof(AuthorizationState));
I'd like TFS 2010 to run a bit of custom code whenever a particular workflow transition happens. Is that possible?
I've found documentation about Custom Actions, which seem to be actions that can automatically trigger work item transitions (am I getting that right?) I also found Custom Activities, which are related to Builds. But nothing that serves this particular requirement - am I missing something?
Thanks for your help!
This is very doable.
It is so doable, that there are many ways to do it. One of my favorites is to make a server side plugin. (Note, this only works on TFS 2010)
These blog posts show the basics:
In C#
In VB
Here is some code that I have modified from my open source project TFS Aggregator:
public class WorkItemChangedEventHandler : ISubscriber
{
/// <summary>
/// This is the one where all the magic starts. Main() so to speak.
/// </summary>
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs,
out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
{
statusCode = 0;
properties = null;
statusMessage = String.Empty;
try
{
if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
{
// Change this object to be a type we can easily get into
WorkItemChangedEvent ev = notificationEventArgs as WorkItemChangedEvent;
// Connect to the setting file and load the location of the TFS server
string tfsUri = TFSAggregatorSettings.TFSUri;
// Connect to TFS so we are ready to get and send data.
Store store = new Store(tfsUri);
// Get the id of the work item that was just changed by the user.
int workItemId = ev.CoreFields.IntegerFields[0].NewValue;
// Download the work item so we can update it (if needed)
WorkItem eventWorkItem = store.Access.GetWorkItem(workItemId);
if ((string)(eventWorkItem.Fields["State"].Value) == "Done")
{
// If the estimated work was changed then revert it back.
// We are in done and don't want to allow changes like that.
foreach (IntegerField integerField in ev.ChangedFields.IntegerFields)
{
if (integerField.Name == "Estimated Work")
{
eventWorkItem.Open();
eventWorkItem.Fields["Estimated Work"].Value = integerField.OldValue;
eventWorkItem.Save();
}
}
}
}
}
}
return EventNotificationStatus.ActionPermitted;
}
public string Name
{
get { return "SomeName"; }
}
public SubscriberPriority Priority
{
get { return SubscriberPriority.Normal; }
}
public WorkItemChangedEventHandler()
{
//DON"T ADD ANYTHING HERE UNLESS YOU REALLY KNOW WHAT YOU ARE DOING.
//TFS DOES NOT LIKE CONSTRUCTORS HERE AND SEEMS TO FREEZE WHEN YOU TRY :(
}
public Type[] SubscribedTypes()
{
return new Type[1] { typeof(WorkItemChangedEvent) };
}
}
/// <summary>
/// Singleton Used to access TFS Data. This keeps us from connecting each and every time we get an update.
/// </summary>
public class Store
{
private readonly string _tfsServerUrl;
public Store(string tfsServerUrl)
{
_tfsServerUrl = tfsServerUrl;
}
private TFSAccess _access;
public TFSAccess Access
{
get { return _access ?? (_access = new TFSAccess(_tfsServerUrl)); }
}
}
/// <summary>
/// Don't use this class directly. Use the StoreSingleton.
/// </summary>
public class TFSAccess
{
private readonly WorkItemStore _store;
public TFSAccess(string tfsUri)
{
TfsTeamProjectCollection tfs = new TfsTeamProjectCollection(new Uri(tfsUri));
_store = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
}
public WorkItem GetWorkItem(int workItemId)
{
return _store.GetWorkItem(workItemId);
}
}
Here is an example of my singleton pattern
public class TFSSingleton
{
private static TFSSingleton _tFSSingletonInstance;
private TfsTeamProjectCollection _teamProjectCollection;
private WorkItemStore _store;
public static TFSSingleton Instance
{
get
{
if (_tFSSingletonInstance == null)
{
_tFSSingletonInstance = new TFSSingleton();
}
return _tFSSingletonInstance;
}
}
public TfsTeamProjectCollection TeamProjectCollection
{
get { return _teamProjectCollection; }
}
public WorkItemStore RefreshedStore
{
get
{
_store.RefreshCache();
return _store;
}
}
public WorkItemStore Store
{
get { return _store; }
}
private TFSSingleton()
{
NetworkCredential networkCredential = new NetworkCredential("pivotalautomation", "*********", "***********");
// Instantiate a reference to the TFS Project Collection
_teamProjectCollection = new TfsTeamProjectCollection(new Uri("http://********:8080/tfs/**********"), networkCredential);
_store = (WorkItemStore)_teamProjectCollection.GetService(typeof(WorkItemStore));
}
}
and here is how it is referenced.
WorkItemTypeCollection workItemTypes = TFSSingleton.Instance.Store.Projects[projectName].WorkItemTypes;