I have a Web Service for my ASP.NET MVC 3 app which creates a new Sales Order in AX. In AX I have created an OnlineOrder class which has 1 method for now. It is to generate a Sales Reference. Below is the code in my web service:
public AxaptaObject order;
public void CreateOrder()
{
AxaptaStart();
order = Axapta.CreateAxaptaObject("OnlineOrder");
AxaptaStop();
}
public string GetSalesRef(string username, string delivery, string reference)
{
AxaptaStart();
string number = order.Call("orderCreate", username, delivery, reference).ToString();
AxaptaStop();
return number;
}
Then in my Controller I call these methods:
client.CreateOrder();
string number = client.GetSalesRef(user.Username, order.deliverymethod, order.custorder).ToString();
This doesn't work and there is no exception info to show, its just a blank message. I believe the order AxaptaObject is not of type OnlineOrder so it can't call those methods. How would I instantiate the object to use the orderCreate method?
EDIT:
If I do:
public string CreateOrder(string username, string delivery, string reference)
{
AxaptaStart();
order = Axapta.CreateAxaptaObject("OnlineOrder");
string number = order.Call("orderCreate", username, delivery, reference).ToString();
AxaptaStop();
return number;
}
This works, but this isn't a valid solution as I'd like to add more methods to my OnlineOrder object in the future and I dont want to call them all in 1 method on my web service
This will never work as the order object is sort of closed when you call the AxaptaStop method.
I will suggest creating a class implementing IDisposable, then call AxaptaStop in the Dispose method. The AxaptaStart call could go to the constructor. This will allow you to scope the AX context like:
using (var axWrap = new AxWrap())
{
var order = axWrap.onlineOrder();
var number = axWrap.orderCreate(order, username, delivery, reference);
}
The Dispose is automatically called by the using statement.
Related
Our Application (MVC Based) accepts user payment information update request over GET method.Default method used by the application is POST.
Currently if we pass any sensitive information over a GET Method via Querystring, then Request sucessfully works.The reason is that it hits the same Edit Action method in Controller
[HttpGet]
[ValidateRequest(true)]
public ActionResult Edit (parameters)
But what we want is that Any request with sensitive information (like Credit Card etc.) sent over a GET method should be rejected by the application.
Anyhow can we reject GET method through Routing if sensitive information is passed? Please suggest valid approach.
My current route that calls Action is mentioned below:
routes.MapRoute("ChargeInformation", "ChargeInformationt.aspx/{seq}", new { controller = "Payment", action = "Edit", seq = UrlParameter.Optional });
Routing's only responsibility is to map URLs to route values and from route values back to URLs. It is a separate concern than authorizing the request. In fact, the built-in routing extension methods (MapRoute, MapPageRoute, and IgnoreRoute) completely ignore the incoming query string.
For request authorization, MVC has an IAuthorizationFilter interface that you can hook into. You can also (optionally) combine it with an attribute to make it run conditionally on specific action methods, as shown below.
In this case, you just want to reject specific query string key names that are passed into the request. It is unclear what action you wish to take in this case, so I am just setting to HTTP 403 forbidden as an example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Web.Mvc;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DisallowQueryStringKeysAttribute : FilterAttribute, IAuthorizationFilter
{
private readonly IEnumerable<string> keysSplit;
public DisallowQueryStringKeysAttribute(string keys)
{
this.keysSplit = SplitString(keys);
}
public void OnAuthorization(AuthorizationContext filterContext)
{
var queryStringKeys = filterContext.HttpContext.Request.QueryString.AllKeys;
// If any of the current query string keys overlap with the non-authorized keys
if (queryStringKeys.Intersect(this.keysSplit, StringComparer.OrdinalIgnoreCase).Any())
{
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
// You must set the result property to a handler to run to tell the
// framework that the filter should do something other than run the
// action method. In this case, we just set it to an empty result,
// which implements the null object pattern. You could (if so inclined),
// make a class to set the status code or do something else
// (such as redirect) to indicate that the request is invalid.
filterContext.Result = new EmptyResult();
}
}
private string[] SplitString(string original)
{
if (String.IsNullOrEmpty(original))
{
return new string[0];
}
var split = from piece in original.Split(',')
let trimmed = piece.Trim()
where !String.IsNullOrEmpty(trimmed)
select trimmed;
return split.ToArray();
}
}
Usage
[HttpGet]
[ValidateRequest(true)]
[DisallowQueryStringKeys("creditCard, password")]
public ActionResult Edit (string creditCard, string password)
Situation
We have a controller where users can submit any number of E-Mail addresses to invite other (potential) members as friends. If an address is not found in the database, we send an E-Mail message to that user. Since the user does not has to wait for this process to complete in order to continue working this is done asynchronously.
Sending E-Mails can take a long time if servers respond slowly, are down or overloaded. The E-Mail sender should update the database according to the status received from the E-Mail server, so for example setting the friend request into "Error" state, when a permanent failure occurs, for example if the address does not exists. For this purpose, the E-Mail component implements the function SendImmediateAsync(From,To,Subject,Content,Callback,UserArg). After the message has been delivered (or it failed), the Callback is called with certain arguments about the Delivery state.
When it eventually calls the delegate, the DbContext object has already been disposed (since the controller has been too) and I cannot manually create a new one using new ApplicationDbContext() because there is no constructor that accepts a connection string.
Question
How can I write to the database long after the controller has been disposed? I have not yet figured out how to manually create a DbContext object for myself. An object of type ApplicationDbContext is passed to the constructor of the Controller and I hoped I could instantiate one for myself, but the Constructor has no arguments I can supply (for example connection string). I want to avoid to manually create an SQL Connection and assemble INSERT statements manually and would prefer to work with the entity model we have already set up.
Code
The code shows the affected segment only without any error checking for readability.
[Authorize]
public class MembersController : Controller
{
private ApplicationDbContext _context;
public MembersController(ApplicationDbContext context)
{
_context = context;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT,
delegate (Guid G, object any)
{
//THIS IS NOT WORKING BECAUSE _context IS DISPOSED
var ctx = _context;
Guid Result = (Guid)any; //user supplied argument
if (G != Guid.Empty)
{
ctx.MailConfirmation.Add(new MailConfirmation()
{
EntryId = Result,
For = EntryFor.FriendRequest,
Id = G
});
if (G == MailHandler.ErrorGuid)
{
var frq = _context.FriendRequest.SingleOrDefault(m => m.Id == Result);
frq.Status = FriendStatus.Error;
ctx.Update(frq);
}
ctx.SaveChanges();
}
}, req.Id);
//rendering view
}
}
First of all, when you are using EF Core with ASP.NET Core's dependency injection, each DbContext instance is scoped per-request, unless you have specified otherwise in ".AddDbContext". This means you should not attempt to re-use an instance of DbContext after that HTTP request has completed. See https://docs.asp.net/en/latest/fundamentals/dependency-injection.html#service-lifetimes-and-registration-options
DbContextOptions, on the other hand, are singletons and can be re-used across requests.
If you need to close the HTTP request and perform an action afterwards, you'll need to create a new DbContext scope an manage it's lifetime.
Second of all, you can overload DbContext's base constructor and pass in DbContextOptions directly. See https://docs.efproject.net/en/latest/miscellaneous/configuring-dbcontext.html
Together, this is what a solution might look like.
public class MembersController : Controller
{
private DbContextOptions<ApplicationDbContext> _options;
public MembersController(DbContextOptions<ApplicationDbContext> options)
{
_options = options;
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Friends()
{
MailHandler.SendImmediateAsync(FROM,TO,SUBJECT,CONTENT, CreateDelegate(_options) req.Id);
}
private static Action<Guid, object> CreateDelegate(DbContextOptions<ApplicationDbContext> options)
{
return (G, any) =>
{
using (var context = new ApplicationDbContext(options))
{
//do work
context.SaveChanges();
}
};
}
}
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base (options) { }
// the rest of your stuff
}
This assumes, of course, that your "MailHandler" class is properly using concurrency to run the delegate so it doesn't block the thread processing the HTTP request.
Why not just pass the dbContext as the userArgs to your SendImmediateAsync? Then the dbContext will not get disposed and can be passed back when you do the callback. I'm pretty sure that should work.
I am exposing my repository operations through web api. Repository has been implemented with Entity framework and Unit Of Work Pattern. I have many instances of the same database. Each one represent the data of a different Client. Now the issue is how can I set the connection string dynamically through each webapi call? Should I get connection string parameter with each call ? Or I should host web Api per client ?
Based on the information provided, I would use the same controller and look up the connection string rather than rather than hosting separate Web API instances for each client. There would be more complexity in hosting multiple instances and given the only difference indicated is the connection string, I do not think the complexity would be justified.
The first thing we will need to do is determine which client is calling in order to get the appropriate connection string. This could be done with tokens, headers, request data, or routing. Routing is simplest and most generally accessible to clients, so I will demonstrate using it; however, carefully consider your requirements in deciding how you will make the determination.
[Route( "{clientId}" )]
public Foo Get( string clientId ) { /* ... */ }
Next we need to get the right DbContext for the client. We want to keep using DI but that is complicated in that we do not know until after the Controller is created what connection string is needed to construct the object. Therefore, we need to inject some form of factory rather than the object itself. In this case we will represent this as a Func<string, IUnitOfWork> with the understanding it takes the 'clientId' as a string and returns an appropriately instantiated IUnitOfWork. We could alternatively use a named interface for this.
[RoutePrefix("foo")]
public class FooController : ApiController
{
private Func<string, IUnitOfWork> unitOfWorkFactory;
public FooController( Func<string, IUnitOfWork> unitOfWorkFactory )
{
this.unitOfWorkFactory = unitOfWorkFactory;
}
[Route( "{clientId}" )]
public Foo Get( string clientId )
{
var unitOfWork = unitOfWorkFactory(clientId);
// ...
}
}
All that remains is configuring our dependency injection container to provide us that Func<string, IUnitOfWork>. This could vary significantly between implementation. The following is one possible way to do it in Autofac.
protected override void Load( ContainerBuilder builder )
{
// It is expected `MyDbContext` has a constructor that takes the connection string as a parameter
// This registration may need to be tweaked depending on what other constructors you have.
builder.Register<MyDbContext>().ForType<DbContext>().InstancePerRequest();
// It is expected `UnitOfWork`'s constructor takes a `DbContext` as a parameter
builder.RegisterType<UnitOfWork>().ForType<IUnitOfWork>().InstancePerRequest();
builder.Register<Func<string, Bar>>(
c =>
{
var dbContextFactory = c.Resolve<Func<string, DbContext>>();
var unitOfWorkFactory = c.Resolve<Func<DbContext, IUnitOfWork>>();
return clientId =>
{
// You may have injected another type to help with this
var connectionString = GetConnectionStringForClient(clientId);
return unitOfWorkFactory(dbContextFactory(connectionString));
};
});
}
Autofac is used since comments indicates Autofac is currently being used, though similar results would be possible with other containers.
With that the controller should be able to be instantiated and the appropriate connection string will be used for each request.
Example registration based on linked project:
builder.Register<Func<string, IEmployeeService>>(
c =>
{
var dbContextFactory = c.Resolve<Func<string, IMainContext>>();
var unitOfWorkFactory = c.Resolve<Func<IMainContext, IUnitOfWork>>();
var repositoryFactory = c.Resolve<Func<IMainContext, IEmployeeRepository>>();
var serviceFactory = c.Resolve<Func<IUnitOfWork, IEmployeeService>>();
return clientId =>
{
// You may have injected another type to help with this
var connectionString = GetConnectionStringForClient(clientId);
IMainContext dbContext = dbContextFactory(connectionString);
IUnitOfWork unitOfWork = unitOfWorkFactory(dbContext);
IEmployeeRepository employeeRepository = repositoryFactory(dbContext);
unitOfWork.employeeRepositoty = employeeRepository;
return serviceFactory(unitOfWork);
};
});
If you find the registration grows too cumbersome because of needing to do a little wiring manually, you probably need to look at updating (or creating a new) container after you have determined the client so that you can rely more on the container.
You can change the connectionstring per DbContext instance
Example:
public class AwesomeContext : DbContext
{
public AwesomeContext (string connectionString)
: base(connectionString)
{
}
public DbSet<AwesomePeople> AwesomePeoples { get; set; }
}
And then use your DbContext like this:
using(AwesomeContext context = new AwesomeContext("newConnectionString"))
{
return context.AwesomePeoples.ToList();
}
Depending on how many ConnectionStrings there are you can make a DB table for the client / constring mapping or save it in the solution (array for example).
If you can't/don't want to change the constructor you can do it later as well
Add this to your DbContext override:
public void SetConnectionString(string connectionString)
{
this.Database.Connection.ConnectionString = connectionString;
}
And call the method before you do any DB operations:
using(AwesomeContext context = new AwesomeContext())
{
context.SetConnectionString(ConfigurationManager.ConnectionStrings["newConnectionString"].ConnectionString)
return context.AwesomePeoples.ToList();
}
I have the following code in my receiving controller:
[Route("api/StudentUserId/{string}"), HttpGet]
public StudentDto StudentUserId(string userId)
{
StudentModel sm = new StudentModel(userId);
StudentDto dto = sm.ConvertToDto();
return dto;
}
After running this project, I have another project that I use to test the WebAPI controller calls. I use the following code to read a student record form the database using their userId:
protected T SendRequestToReadItemUsingString<T>(string resource, string userId) where T : new()
{
string resourceString = string.Format("{0}/{{userId}}", resource);
RestRequest request = new RestRequest(resourceString, Method.GET);
request.AddUrlSegment("userId", userId);
RestClient restClient = new RestClient(Service.Location);
var response = restClient.Execute<T>(request);
T retVal = response.Data;
return retVal;
}
Comparible code seems to work if I change the userId to an int Id in both the controller and calling code. I can't seem to get it to work with string. If I place a breakpoint in the controller code it never hits it and the calling code just returns a null.
Thanks for your help
Please note that WebApi works based on reflection this means that your curly braces {vars} must match the same name in your methods.
Therefore to match this api/StudentUserId/{string} your method needs to be declare like this:
[Route("api/StudentUserId/{userId}"), HttpGet]
public StudentDto StudentUserId(string userId)
return userId;
}
Where the parameter {string} was replaced by userId.
If you want to read more about this Routing Rules here is similar post on this;
WebApi Routing Configuration
Is it possible for a Controller method to handle all Posted items which derive from a particular base class? The idea is to be able to dispatch Commands by posting them to an endpoint. When I try the following, the "cmd" parameter in the Post method is always null.
Example
//the model:
public abstract class Command{
public int CommandId{get; set;}
}
public class CommandA:Command{
public string StringParam{get; set;}
}
public class CommandB:Command{
public DateTime DateParam{get; set;}
}
//and in the controller:
public HttpResponseMessage Post([FromBody]Command cmd)
{
//cmd parameter is always null when I Post a CommandA or CommandB
//it works if I have separate Post methods for each Command type
if (ModelState.IsValid)
{
if (cmd is CommandA)
{
var cmdA = (CommandA)cmd;
// do whatever
}
if (cmd is CommandB)
{
var cmdB = (CommandB)cmd;
//do whatever
}
//placeholder return stuff
var response = Request.CreateResponse(HttpStatusCode.Created);
var relativePath = "/api/ToDo/" + cmd.TestId.ToString();
response.Headers.Location = new Uri(Request.RequestUri, relativePath);
return response;
}
throw new HttpResponseException(HttpStatusCode.BadRequest);
}
Again, when I try this approach the Post method gets called, but the parameter is always null from the framework. However if I replace it with a Post method with a specific CommandA parameter type, it works.
Is what I'm attempting possible? Or does every message type need a separate handler method in the controller?
If you are sending data in Json format, then following blog gives more details about how hierarchies deserialization can be achieved in json.net:
http://dotnetbyexample.blogspot.com/2012/02/json-deserialization-with-jsonnet-class.html