I use Entity Framework to deal with my database, and as I googled a lot, I ran into some solution around updating my entities, here is extension method for updating entity the code :
public static void AttachUpdated(this ObjectContext context, EntityObject objectDetached)
{
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (context.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
context.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
//(CDLTLL)Apply property changes to all referenced entities in context
//Custom extensor method
context.ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb);
}
else
{
throw new ObjectNotFoundException();
}
}
}
public static void ApplyReferencePropertyChanges(this ObjectContext context,
IEntityWithRelationships newEntity,
IEntityWithRelationships oldEntity)
{
foreach (var relatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = relatedEnd as EntityReference;
if (oldRef != null)
{
// this related end is a reference not a collection
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
oldRef.EntityKey = newRef.EntityKey;
}
}
}
And here is the code for saving or adding new object :
public void save (Category obj)
{
OurWebSiteEntities en = new OurWebSiteEntities();
if (obj.CategoryID == -1)
{
en.AddToCategories(obj);
}
else
en.AttachUpdated(obj);
en.SaveChanges();
}
And finally this the code I use to update my category entity in asp.net mvc :
if (cat.Category.CategoryID == -1)
{
// it is a new category
// and needs some property initialization
}
else
catItem = _categoryRepo.Items.FirstOrDefault(x => x.CategoryID == cat.Category.CategoryID);
TryUpdateModel(catItem, "Category");
_categoryRepo.Save(catItem);
But unfortunately it does not work for updating any category, and I am only able to create new one.
In your repository, you could create an update function and set entity state to modified.
public abstract class Repository<T>
{
public void Update(T entity) {
Context.Entry(entity).State = EntityState.Modified;
}
}
Related
I got an MVC 5 application that i'm porting to asp.net Core.
In the MVC application call to controller we're made using AngularJS $resource (sending JSON) and we we're POSTing data doing :
ressource.save({ entries: vm.entries, projectId: vm.project.id }).$promise...
that will send a JSON body like:
{
entries:
[
{
// lots of fields
}
],
projectId:12
}
the MVC controller looked like this :
[HttpPost]
public JsonResult Save(List<EntryViewModel> entries, int projectId) {
// code here
}
How can I replicate the same behaviour with .NET Core since we can't have multiple [FromBody]
you cannot have multiple parameter with the FromBody attibute in an action method. If that is need, use a complex type such as a class with properties equivalent to the parameter or dynamic type like that
[HttpPost("save/{projectId}")]
public JsonResult Save(int projectId, [FromBody] dynamic entries) {
// code here
}
As pointed out in the comment, one possible solution is to unify the properties you're posting onto a single model class.
Something like the following should do the trick:
public class SaveModel
{
public List<EntryViewModel> Entries{get;set;}
public int ProjectId {get;set;}
}
Don't forget to decorate the model with the [FromBody] attribute:
[HttpPost]
public JsonResult Save([FromBody]SaveViewModel model)
{
// code here
}
Hope this helps!
It's still rough but I made a Filter to mimic the feature.
public class OldMVCFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Method != "GET")
{
var body = context.HttpContext.Request.Body;
JToken token = null;
var param = context.ActionDescriptor.Parameters;
using (var reader = new StreamReader(body))
using (var jsonReader = new JsonTextReader(reader))
{
jsonReader.CloseInput = false;
token = JToken.Load(jsonReader);
}
if (token != null)
{
var serializer = new JsonSerializer();
serializer.DefaultValueHandling = DefaultValueHandling.Populate;
serializer.FloatFormatHandling = FloatFormatHandling.DefaultValue;
foreach (var item in param)
{
JToken model = token[item.Name];
if (model == null)
{
// try to cast the full body as the current object
model = token.Root;
}
if (model != null)
{
model = this.RemoveEmptyChildren(model, item.ParameterType);
var res = model.ToObject(item.ParameterType, serializer);
context.ActionArguments[item.Name] = res;
}
}
}
}
}
private JToken RemoveEmptyChildren(JToken token, Type type)
{
var HasBaseType = type.GenericTypeArguments.Count() > 0;
List<PropertyInfo> PIList = new List<PropertyInfo>();
if (HasBaseType)
{
PIList.AddRange(type.GenericTypeArguments.FirstOrDefault().GetProperties().ToList());
}
else
{
PIList.AddRange(type.GetTypeInfo().GetProperties().ToList());
}
if (token != null)
{
if (token.Type == JTokenType.Object)
{
JObject copy = new JObject();
foreach (JProperty jProp in token.Children<JProperty>())
{
var pi = PIList.FirstOrDefault(p => p.Name == jProp.Name);
if (pi != null) // If destination type dont have this property we ignore it
{
JToken child = jProp.Value;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, pi.PropertyType);
}
if (!IsEmpty(child))
{
if (child.Type == JTokenType.Object || child.Type == JTokenType.Array)
{
// nested value has been checked, we add the object
copy.Add(jProp.Name, child);
}
else
{
if (!pi.Name.ToLowerInvariant().Contains("string"))
{
// ignore empty value when type is not string
var Val = (string)child;
if (!string.IsNullOrWhiteSpace(Val))
{
// we add the property only if it contain meningfull data
copy.Add(jProp.Name, child);
}
}
}
}
}
}
return copy;
}
else if (token.Type == JTokenType.Array)
{
JArray copy = new JArray();
foreach (JToken item in token.Children())
{
JToken child = item;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, type);
}
if (!IsEmpty(child))
{
copy.Add(child);
}
}
return copy;
}
return token;
}
return null;
}
private bool IsEmpty(JToken token)
{
return (token.Type == JTokenType.Null || token.Type == JTokenType.Undefined);
}
}
I am following the sample blog below to remove and add properties in request ODataEntry class.
http://blogs.msdn.com/b/odatateam/archive/2013/07/26/using-the-new-client-hooks-in-wcf-data-services-client.aspx
But even if the code works fine and adds and removes the properties correctly when I put breakpoint, all the entity properties goes to server un changed.
Only difference I see this I am using the OData V4 and new Ondata client to hook up.
My code looks below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Client.Default;
namespace Client
{
using Client.MvcApplication1.Models;
using Microsoft.OData.Core;
internal class Program
{
private static void Main(string[] args)
{
Container container = new Container(new Uri("http://localhost:55000/api/"));
container.Configurations.RequestPipeline.OnEntryEnding(
w =>
{
w.Entry.RemoveProperties("Name");
});
Test test = new Test();
test.Name = "Foo";
CustomFields cs = new CustomFields { ServiceId = 3 };
cs.Foo1 = 2;
test.S_1 = cs;
container.AddToTests(test);
container.SaveChanges();
}
}
public static class Extensions
{
public static void RemoveProperties(this ODataEntry entry, params string[] propertyNames)
{
var properties = entry.Properties as List<ODataProperty>;
if (properties == null)
{
properties = new List<ODataProperty>(entry.Properties);
}
var propertiesToRemove = properties.Where(p => propertyNames.Any(rp => rp == p.Name));
foreach (var propertyToRemove in propertiesToRemove.ToArray())
{
properties.Remove(propertyToRemove);
}
entry.Properties = properties;
}
public static void AddProperties(this ODataEntry entry, params ODataProperty[] newProperties)
{
var properties = entry.Properties as List<ODataProperty>;
if (properties == null)
{
properties = new List<ODataProperty>(entry.Properties);
}
properties.AddRange(newProperties);
entry.Properties = properties;
}
}
}
If I change and start listening to RequestPipeline.OnEntryStarting I get the validation error that new property is not defined in owning entity. But as per code for Microsoft.OData.CLient this error should not occure as there is a check for IEdmStructuredType.IsOpen but still error occurs. So issue seems deep in how owningStructuredType is calculated. On my container I do see the correct edm model with entities marked as IsOpen = true.
Odata lib code which should pass but is failing
internal static IEdmProperty ValidatePropertyDefined(string propertyName, IEdmStructuredType owningStructuredType)
{
Debug.Assert(!string.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)");
if (owningStructuredType == null)
{
return null;
}
IEdmProperty property = owningStructuredType.FindProperty(propertyName);
// verify that the property is declared if the type is not an open type.
if (!owningStructuredType.IsOpen && property == null)
{
throw new ODataException(Strings.ValidationUtils_PropertyDoesNotExistOnType(propertyName, owningStructuredType.ODataFullName()));
}
return property;
}
Client code:
container.Configurations.RequestPipeline.OnEntryStarting(
w =>
{
w.Entry.RemoveProperties("Name");
w.Entry.AddProperties(new ODataProperty
{
Name = "NewProperty",
Value = 1
});
});
Error:
The property 'NewProperty' does not exist on type 'Client.MvcApplication1.Models.Test'. Make sure to only use property names that are defined by the type.
at Microsoft.OData.Core.WriterValidationUtils.ValidatePropertyDefined(String propertyName, IEdmStructuredType owningStructuredType)
at Microsoft.OData.Core.JsonLight.ODataJsonLightPropertySerializer.WriteProperty(ODataProperty property, IEdmStructuredType owningType, Boolean isTopLevel, Boolean allowStreamProperty, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties)
at Microsoft.OData.Core.JsonLight.ODataJsonLightPropertySerializer.WriteProperties(IEdmStructuredType owningType, IEnumerable`1 properties, Boolean isComplexValue, DuplicatePropertyNamesChecker duplicatePropertyNamesChecker, ProjectedPropertiesAnnotation projectedProperties)
at Microsoft.OData.Core.JsonLight.ODataJsonLightWriter.StartEntry(ODataEntry entry)
at Microsoft.OData.Core.ODataWriterCore.<>c__DisplayClass14.<WriteStartEntryImplementation>b__12()
at Microsoft.OData.Core.ODataWriterCore.InterceptException(Action action)
at Microsoft.OData.Core.ODataWriterCore.WriteStartEntryImplementation(ODataEntry entry)
at Microsoft.OData.Core.ODataWriterCore.WriteStart(ODataEntry entry)
at Microsoft.OData.Client.ODataWriterWrapper.WriteStart(ODataEntry entry, Object entity)
at Microsoft.OData.Client.Serializer.WriteEntry(EntityDescriptor entityDescriptor, IEnumerable`1 relatedLinks, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.BaseSaveResult.CreateRequestData(EntityDescriptor entityDescriptor, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.BaseSaveResult.CreateChangeData(Int32 index, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.SaveResult.CreateNonBatchChangeData(Int32 index, ODataRequestMessageWrapper requestMessage)
at Microsoft.OData.Client.SaveResult.CreateNextChange()
I use partial classes defined on the client to add the extra properties that I need there. This allows me to put any logic in them as well as have property changed notification as well. I the use the following extension methods to remove those properties. I think I actually got the original code from the article that you linked.
public static class DbContextExtensions
{
public static void RemoveProperties(this ODataEntry entry, params string[] propertyNames)
{
var properties = entry.Properties as List<ODataProperty>;
if (properties == null)
{
properties = new List<ODataProperty>(entry.Properties);
}
var propertiesToRemove = properties.Where(p => propertyNames.Any(rp => rp == p.Name));
foreach (var propertyToRemove in propertiesToRemove.ToArray())
{
properties.Remove(propertyToRemove);
}
entry.Properties = properties;
}
public static DataServiceClientResponsePipelineConfiguration RemoveProperties<T>(this DataServiceClientResponsePipelineConfiguration responsePipeline, Func<string, Type> resolveType, params string[] propertiesToRemove)
{
return responsePipeline.OnEntryEnded((args) =>
{
Type resolvedType = resolveType(args.Entry.TypeName);
if (resolvedType != null && typeof(T).IsAssignableFrom(resolvedType))
{
args.Entry.RemoveProperties(propertiesToRemove);
}
});
}
public static DataServiceClientRequestPipelineConfiguration RemoveProperties<T>(this DataServiceClientRequestPipelineConfiguration requestPipeline, params string[] propertiesToRemove)
{
return requestPipeline.OnEntryStarting((args) =>
{
if (typeof(T).IsAssignableFrom(args.Entity.GetType()))
{
args.Entry.RemoveProperties(propertiesToRemove);
}
});
}
}
Notice that in the method below it is hooking OnEntryStarted. The code in the article hooks OnEntryEnded which worked for me at one point and then broke when I updated to a newer version of ODataClient. OnEntryStarted is the way to go in this method.
public static DataServiceClientRequestPipelineConfiguration RemoveProperties<T>(this DataServiceClientRequestPipelineConfiguration requestPipeline, params string[] propertiesToRemove)
{
return requestPipeline.OnEntryStarting((args) =>
{
if (typeof(T).IsAssignableFrom(args.Entity.GetType()))
{
args.Entry.RemoveProperties(propertiesToRemove);
}
});
}
I also created a partial class for the Container as well and implement the partial method OnContextCreated. This is where you use the extension methods to remove the properties that won't get sent to the server.
partial void OnContextCreated()
{
Configurations.RequestPipeline.RemoveProperties<Customer>(new string[] { "FullName", "VersionDetails" });
Configurations.RequestPipeline.RemoveProperties<SomeOtherType>(new string[] { "IsChecked", "IsReady" });
}
Make sure that your partial classes and the DBContextExtensions class are in the same namespace as our container and everything should just work.
Hope that helps.
I've been tasked with taking over an existing ASP.NET MVC 2.0 web application that was developed by a third party developer who is no longer around to provide any assistance. There has been a requirement to add some functionality to the project, which required a project upgrade to .NET 4.5, which has been performed.
The sites underlying MSSQL 2008 R2 database access has been implemented using NHibernate version 2.0.1.4000, along with Castle and FluentNHibernate.
This is the first project I've been involved in that has used NHibernate, and I've hit a problem that has me stumped. The problem did not exist until the upgrade to .NET 4.5.
All database operations are working normally, except for one. Saving a particular object (
Opportunity type) to the database (this object directly maps to an Opportunity database table) fails. Prior to saving (in this case a SQL UPDATE statement), the object has new values set. But the record in the database always has the old values after saving.
Hooking up log4net to view the debug code, shows that the record is indeed updated, but using the old values in the UPDATE statement.
Surprisingly, the Opportunity object is intially saved using the same Save method (albeit via a different action method), and that is saving to the database just fine.
So my question is, what would cause this to happen? Being that I'm not an NHibernate expert, is it the case that the NHibernate version is simply incompatible with .NET 4.5? Or can anyone provide a pointer as to what the problem might be? I'm happy to show any code, but as there is so much I would need to know what. Below is a starter:
The Global.asax has the following references to NHibernate:
private static void MvcApplication_BeginRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.BeginTransaction();
}
private static void MvcApplication_EndRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.CommitTransaction();
}
The NHibernateSessionManager class is defined as (Opportunity derives from DomainBase):
public sealed class NHibernateSessionManager
{
private ISessionFactory sessionFactory;
private Configuration config;
#region Thread-safe, lazy Singleton
public static NHibernateSessionManager Instance
{
get
{
return Nested.nHibernateSessionManager;
}
}
private NHibernateSessionManager()
{
InitSessionFactory();
}
private class Nested
{
internal static readonly NHibernateSessionManager nHibernateSessionManager = new NHibernateSessionManager();
}
#endregion
private void InitSessionFactory()
{
var autoMappings = AutoPersistenceModel.MapEntitiesFromAssemblyOf<DomainBase>()
.Where(type =>
typeof(DomainBase).IsAssignableFrom(type) &&
type.IsClass &&
!type.IsAbstract)
.WithSetup(s =>
{
s.IsBaseType = type =>
type == typeof (DomainBase);
})
.UseOverridesFromAssemblyOf<OpportunityMappingOverride>()
.ConventionDiscovery.Add(DefaultLazy.AlwaysTrue())
.ConventionDiscovery.Add<CascadeAllHasOneConvention>()
.ConventionDiscovery.Add<CascadeAllHasManyConvention>()
.ConventionDiscovery.Add<CascadeAllReferenceConvention>();
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString(c => c.FromConnectionStringWithKey("Default"))
.UseReflectionOptimizer()
.Cache(c => c.UseQueryCache().UseMininmalPuts().ProviderClass<SysCacheProvider>())
.ShowSql())
.Mappings(m => m.AutoMappings.Add(autoMappings))
.ExposeConfiguration(SetConfiguration)
.BuildSessionFactory();
}
private void SetConfiguration(Configuration cfg)
{
config = cfg;
}
public void RegisterInterceptor(IInterceptor interceptor)
{
ISession session = threadSession;
if (session != null && session.IsOpen)
{
throw new CacheException("You cannot register an interceptor once a Session has already been opened");
}
GetSession(interceptor);
}
public void GenerateSchema()
{
new SchemaExport(config).Execute(false, true, false, false);
}
public ISession GetSession()
{
return GetSession(null);
}
private ISession GetSession(IInterceptor interceptor)
{
ISession session = threadSession;
if (session == null)
{
if (interceptor != null)
{
session = sessionFactory.OpenSession(interceptor);
}
else
{
session = sessionFactory.OpenSession();
}
threadSession = session;
}
return session;
}
public void CloseSession()
{
ISession session = threadSession;
threadSession = null;
if (session != null && session.IsOpen)
{
session.Close();
}
}
public void BeginTransaction()
{
ITransaction transaction = threadTransaction;
if (transaction == null)
{
transaction = GetSession().BeginTransaction();
threadTransaction = transaction;
}
}
public void CommitTransaction()
{
ITransaction transaction = threadTransaction;
try
{
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Commit();
threadTransaction = null;
}
}
catch (HibernateException)
{
RollbackTransaction();
throw;
}
}
public void RollbackTransaction()
{
ITransaction transaction = threadTransaction;
try
{
threadTransaction = null;
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Rollback();
}
}
finally
{
CloseSession();
}
}
private static ITransaction threadTransaction
{
get
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
set
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
private static ISession threadSession
{
get
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
set
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}
I'm hoping I won't get shot down for this question being too general. I've spent a day trying to work out what is happening, including extensive searches online.
It turned out the problem was that the NHibernateSessionManager class was storing its ITransaction and ISession objects in System.Runtime.Remoting.Messaging.CallContext.
Swapping it out to store the objects in the HttpContext.Current.Items collection resolved the issue.
I found this post which implies .NET 4.5 handles CallContext slightly differently compared with previous versions, which obviously caused my issue.
Because the NHibernateSessionManager class was in a class library that was also used by a couple of rarely used console applications, I left a fallback to the CallContext object as per below (not pretty, and there might have been an better alternative, but worked for me [subject to testing], as I've spent far to long figuring this one out using remote debugging):
private static ITransaction threadTransaction
{
get
{
try
{
return (ITransaction)System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"];
}
catch
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"] = value;
}
catch
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
}
private static ISession threadSession
{
get
{
try
{
return (ISession)System.Web.HttpContext.Current.Items["THREAD_SESSION"];
}
catch
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_SESSION"] = value;
}
catch
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}
In a ASP.NET MVC project I'm working on I have the following piece of code that basically inject instances to specific methods within my assemblies.
So in the application root I have a class that register the instances like this and finally handles the injection.
ApplicationServiceProvider serviceProvider = ApplicationServiceProvider.CreateDefaultProvider();
serviceProvider.RegisterInstance(GlobalConfiguration.Configuration);
serviceProvider.RegisterInstance(GlobalFilters.Filters);
serviceProvider.RegisterInstance(RouteTable.Routes);
serviceProvider.RegisterInstance(BundleTable.Bundles);
serviceProvider.Distribute();
Now when I want to access these instances from the assemblies, I have to create some handler (method) and mark it with the following attribute 'ApplicationServiceHandler' like in the following example.
[ContractVerification(false)]
public static class RouteConfiguration
{
[ApplicationServiceHandler]
public static void Register(RouteCollection routes)
{
}
}
This is part of the extensibility layer in the project which is currently working pretty good.
Now, I'm new to Autofac and I wonder whether I can use Autofac to do the work for me rather than using my own implementation (which I provided below) that probably does it less efficient and handles less cases that are already covered by Autofac.
I noticed Autofac have a RegisterInstance method but I'm not sure how to tell it to inject the instances to methods flagged with 'ApplicationServiceHandler' attribute, I'm not not sure it's even the correct method but based on the name it seems like the right one.
Any kind of help is greatly appreciated, thank you.
EDIT: Here is the code that I'm using to achieve this without Autofac in my project.
ApplicationServiceHandlerAttribute.cs
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ApplicationServiceHandlerAttribute : Attribute
{
}
ApplicationServiceHandler.cs
public sealed class ApplicationServiceHandler
{
private readonly MethodInfo _method;
private readonly object[] _args;
public ApplicationServiceHandler(MethodInfo method, object[] args)
{
Contract.Requires(method != null);
Contract.Requires(args != null);
_method = method;
_args = args;
}
public void Invoke()
{
_method.Invoke(null, _args);
}
[ContractInvariantMethod]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
private void ObjectInvariant()
{
Contract.Invariant(_method != null);
Contract.Invariant(_args != null);
}
}
ApplicationServiceProvider.cs
public sealed class ApplicationServiceProvider
{
private readonly IEnumerable<Assembly> _assemblies;
private readonly Dictionary<Type, object> _instances;
public ApplicationServiceProvider(IEnumerable<Assembly> assemblies)
{
Contract.Requires(assemblies != null);
_assemblies = assemblies;
_instances = new Dictionary<Type, object>();
}
public static ApplicationServiceProvider CreateDefaultProvider()
{
Contract.Ensures(Contract.Result<ApplicationServiceProvider>() != null);
return new ApplicationServiceProvider(PackageLoader.ReferencedAssemblies);
}
public void Distribute()
{
foreach (var handler in GetHandlers())
{
Contract.Assume(handler != null);
handler.Invoke();
}
}
public IEnumerable<ApplicationServiceHandler> GetHandlers()
{
Contract.Ensures(Contract.Result<IEnumerable<ApplicationServiceHandler>>() != null);
if (_instances.Count == 0)
{
yield break;
}
foreach (var asm in _assemblies)
{
IEnumerable<MethodInfo> methods = GetMethods(asm);
foreach (var method in methods)
{
ParameterInfo[] #params = method.GetParameters();
if (#params.Length > 0)
{
int instanceCount = 0;
object[] args = new object[#params.Length];
for (int i = 0; i < #params.Length; i++)
{
ParameterInfo param = #params[i];
var instance = GetInstance(param);
if (instance != null)
{
instanceCount++;
args[i] = instance;
}
}
if (instanceCount > 0)
{
yield return new ApplicationServiceHandler(method, args);
}
}
}
}
}
public bool RegisterInstance(object instance)
{
Contract.Requires(instance != null);
return AddInstance(instance);
}
private static ApplicationServiceHandlerAttribute GetApplicationServiceHandlerAttribute(MethodInfo method)
{
ApplicationServiceHandlerAttribute attribute = null;
try
{
attribute = method.GetCustomAttribute<ApplicationServiceHandlerAttribute>(false);
}
catch (TypeLoadException)
{
// We don't need to do anything here for now.
}
return attribute;
}
private static IEnumerable<Type> GetDefinedTypes(Assembly assembly)
{
Contract.Requires(assembly != null);
Contract.Ensures(Contract.Result<IEnumerable<Type>>() != null);
try
{
return assembly.DefinedTypes;
}
catch (ReflectionTypeLoadException ex)
{
return ex.Types.Where(type => type != null);
}
}
/// <summary>
/// Gets the methods that are marked with <see cref="ApplicationServiceHandlerAttribute"/> from the assembly.
/// </summary>
/// <remarks>
/// Eyal Shilony, 21/11/2012.
/// </remarks>
/// <param name="assembly">
/// The assembly.
/// </param>
/// <returns>
/// The methods that are marked with <see cref="ApplicationServiceHandlerAttribute"/> from the assembly.
/// </returns>
private static IEnumerable<MethodInfo> GetMethods(Assembly assembly)
{
Contract.Requires(assembly != null);
Contract.Ensures(Contract.Result<IEnumerable<MethodInfo>>() != null);
const TypeAttributes STATIC_TYPE_ATTRIBUTES = TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit;
var methods = (from type in GetDefinedTypes(assembly)
where type.Attributes == STATIC_TYPE_ATTRIBUTES
from method in type.GetMethods().AsParallel()
where GetApplicationServiceHandlerAttribute(method) != null
select method).ToArray();
return methods;
}
private bool AddInstance(object instance)
{
Type type = instance.GetType();
return AddInstance(type, instance);
}
private bool AddInstance(Type type, object instance)
{
if (!_instances.ContainsKey(type))
{
_instances.Add(type, instance);
return true;
}
return false;
}
private object GetInstance(ParameterInfo param)
{
object instance = null;
Type paramType = param.ParameterType;
if (_instances.ContainsKey(paramType))
{
instance = _instances[paramType];
}
else
{
foreach (var type in _instances.Keys.Where(type => type.IsSubclassOf(paramType)))
{
instance = _instances[type];
break;
}
}
return instance;
}
}
i hope , i have understood you correctly.if what you mean is marking a class as dependency with attributes then you can do it by creating custom attribute.following is an example of implementing such an attribute :
public class DependencyAttribute : Attribute
{
public DependencyAttribute()
{
}
//The type of service the attributed class represents
public Type ServiceType { get; set; }
//Optional key to associate with the service
public string Key { get; set; }
public virtual void RegisterService(AttributeInfo<DependencyAttribute> attributeInfo, IContainer container)
{
Type serviceType = attributeInfo.Attribute.ServiceType ?? attributeInfo.DecoratedType;
Containerbuilder builder = new ContainerBuilder();
builder.RegisterType(attributeInfo.DecoratedType).As(serviceType).Keyed(
attributeInfo.Attribute.Key ?? attributeInfo.DecoratedType.FullName);
builder.Update(container)
}
}
then you must find all types marked with this attribute and call the RegisterService method of these attributes.
public class DependencyAttributeRegistrator
{
public DependencyAttributeRegistrator()
{
}
public IEnumerable<AttributeInfo<DependencyAttribute>> FindServices()
{
//replace this line with you'r own
var types = Assembly.GetExecutingAssembly().GetTypes();
foreach (Type type in types)
{
var attributes = type.GetCustomAttributes(typeof(DependencyAttribute), false);
foreach (DependencyAttribute attribute in attributes)
{
yield return new AttributeInfo<DependencyAttribute> { Attribute = attribute, DecoratedType = type };
}
}
}
public void RegisterServices(IEnumerable<AttributeInfo<DependencyAttribute>> services)
{
foreach (var info in services)
{
//replace following two line with you'r own global container
var builder = new ContainerBuilder();
IContainer container = builder.Build();
info.Attribute.RegisterService(info, container);
}
}
}
Given:
request.UrlReferrer.LocalPath = "/MyApp/MyHome/List";
and I have a Route Mapping that handles this where MyHome is my controller and List is an action that takes a ViewModel. Other variations of this Route include paging and sorting but these are captured by the ViewModel.
My question is this:
How can I use the above URL to generate an instance of the related ViewModel?
EDIT: I have an JQuery Dialog that is adding/updating/deleting an item in a list that is shown by the url in the urlreferrer- the example given is the most basic. When the dialog sends the data to be a/u/d, I want to return the updated body of the list and display that. This information is handled by a different ViewModel than what is instantiated on the POST from the dialog (the url posted to is "/MyApp/MyHome/Edit/True" - for creating a new whatever). This piece follows the standard MVC process and of course works. What I want to do is create a second ViewModel based on the ViewModel for the list action and return this as a partial view containing the updated paged list.
Ok... I think I have this figured out. This is not pretty but it works. I welcome anybody's input to actually feed this through a ModelBinder or any other MVC artifact but here's what I came up with:
First we need to fake a request using the UrlReferrer instead of the actual url being requested:
public class FakeHttpContext : HttpContextBase
{
public FakeHttpContext(HttpContextBase currentContext)
{
_request = new FakeHttpRequest(currentContext.Request);
}
HttpRequestBase _request;
public override HttpRequestBase Request
{
get
{
return _request;
}
}
HttpResponseBase _response = new FakeHttpResponse();
public override HttpResponseBase Response
{
get
{
return _response;
}
}
class FakeHttpRequest : HttpRequestBase
{
HttpRequestBase _request;
public FakeHttpRequest(HttpRequestBase currentRequest)
{
if(currentRequest == null)
throw new ArgumentNullException();
this._request = currentRequest;
}
public override string ApplicationPath
{
get
{
return this._request.ApplicationPath;
}
}
public override string AppRelativeCurrentExecutionFilePath
{
get
{
return "~" + this._request.UrlReferrer.AbsolutePath.Remove(0, this._request.ApplicationPath.Length);
}
}
public override string PathInfo
{
get
{
return this._request.PathInfo;
}
}
}
class FakeHttpResponse : HttpResponseBase
{
}
}
Next, we feed the fake call through the RouteTable to get it broken down. and match up properties to the RouteData.Values.
public static class RouteAndModelBinder
{
public static void BuildViewModel<TViewModel>(ControllerContext context, TViewModel model)
{
FakeHttpContext fake = new FakeHttpContext(context.HttpContext);
RouteData test = RouteTable.Routes.GetRouteData(fake);
PropertyInfo[] properties = typeof(TViewModel).GetProperties();
string value;
foreach(PropertyInfo info in properties)
{
if(test.Values.ContainsKey(info.Name))
{
value = (string)test.Values[info.Name];
if(value == null)
{
continue;
}
if(info.PropertyType.IsGenericType &&
info.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Type[] nullables = info.PropertyType.GetGenericArguments();
if(nullables.Length > 0)
{
Type nullableType = nullables[0];
if(nullableType.BaseType == typeof(Enum))
{
object o = Enum.Parse(nullableType, value);
info.SetValue(model, o, null);
}
else if(nullableType == typeof(Int32))
{
info.SetValue(model, int.Parse(value), null);
}
else
{
info.SetValue(model, Convert.ChangeType(value, info.PropertyType), null);
}
}
}
else
{
if(info.PropertyType.BaseType == typeof(Enum))
{
object o = Enum.Parse(info.PropertyType.BaseType, value);
info.SetValue(model, o, null);
}
else if(info.PropertyType == typeof(Int32))
{
info.SetValue(model, int.Parse(value), null);
}
else
{
info.SetValue(model, value, null);
}
}
}
}
}
}
Again, I welcome anybody's suggestions on how I can do this with already established MVC code (ie, ModelBinders, etc). I took some ideas and probably code from here (for the nullable type) and here.