Returning an attachment from a remote web service - asp.net-mvc

Summary
I need to retrieve attachments stored in a parent app from a link in a client of a child app. The attachments are available in the parent app via a web service call -- which returns a standard FileContentResult with content type "application/octet-stream". The best way I can think is to retrieve this via a WebRequest and pass the resulting response stream to a FileStreamResult, though I have some alternatives available.
Does anyone know if, when making a WebRequest, the response stream becomes available immediately once the first part of the response is returned or is it buffered so I don't get the response until all data has been retrieved?
Are there any other options than those listed in the full question below for doing this that I'm missing? (Other than keeping the attachments in both child and parent DBs -- I really don't want to do this since then I'd need to regularly synchronize them, too).
TLDR Version
I have two related applications which communicate through a RESTful web service. The parent application maintains a collection of entities which may have attachments. For example, a Request might have an Excel spreadsheet as an attachment. The entity and its attachment are stored in the database and access to the attachment is controlled using the same logic as access to the Request. That is, you should not be able to download an attachment if you cannot view the Request.
In the child application I maintain some integration glue for the entities assigned to a particular institution -- the app is used to communicate between our Board of Regents and each Regents school. I don't want to maintain and synchronize the full entity/attachment. I only want to maintain enough information to allow me to connect to the web service in the parent app and get the details for entities that the particular instance of the child application has access to.
This works well for the entity data itself. The amount of data is small and the overhead of buffering in the child application doesn't present a signficant delay in accessing the data. If necessary, I could cache the data locally to avoid performance penalities.
My concern is the attachments. I've considered three different mechanisms for providing access to the attachment from a client of the child application.
Generate a one-time use token and associated url that allows the client to directly download the attachment from the parent application. The token generation web service call would ensure that users of the child application should have access to the attachment. The drawback to this is that you'd only be able to click on the link once in the client. Clicking again would result in an error rather than getting the attachment.
Buffer the attachment in the child app. In this scenario I would provide a controller/action to download the attachment in the child app, then call a web service method to get the attachment and have the child app send the attachment as a FileContentResult. This removes the issue of only being able to click the link once, but the attachments could be reasonably large and buffering the data in the child application could potentially double the amount of time to download the attachment and, worse, incur a significant delay before the attachment download begins.
Link in the child app, but provide the stream from the web service request directly to a FileStreamResult. This seems, to me, to be the best option as the FileStreamResult reads in chunks rather than having to have all the data available before it is sent to the client. The only drawback that I can see here is that I can no longer dispose of the WebResponse directly as the FileStreamResult won't be executed until after my action returns.
Here is what I have for the code for API wrapper code for (2) and (3):
private class ResponseModel<T> : IDisposable
{
public T Model { get; set; }
public WebResponse Response { get; set; }
private bool Disposed { get; set; }
private void Dispose( bool disposing )
{
if (!Disposed)
{
if (disposing)
{
((IDisposable)this.Response).Dispose();
}
Disposed = true;
}
}
public void Dispose()
{
Dispose( true );
}
}
private ResponseModel<T> GetAttachmentResponse<T>( long id ) where T : IDownloadModel, new()
{
var request = GetRequest( string.Format( "{0}/api/getattachment/{1}/{2}", this.BaseUrl, this.Key, id ) );
var response = request.GetResponse();
var model = (T)Activator.CreateInstance<T>();
var contentDisposition = response.Headers["Content-Disposition"];
if (!string.IsNullOrEmpty( contentDisposition ))
{
var filename = contentDisposition.Split( new[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries )
.SingleOrDefault( s => s.StartsWith( "filename", StringComparison.OrdinalIgnoreCase ) );
if (!string.IsNullOrEmpty( filename ))
{
model.Name = filename.Split( '=' ).Skip( 1 ).FirstOrDefault();
}
}
if (string.IsNullOrEmpty( model.Name ))
{
model.Name = "untitled";
}
return new ResponseModel<T> { Model = model, Response = response };
}
public FileDownloadModel GetAttachment( long id )
{
using (var response = GetAttachmentResponse<FileDownloadModel>( id ))
{
var reader = new BinaryReader( response.Response.GetResponseStream() );
response.Model.Content = reader.ReadBytes( (int)response.Response.ContentLength );
return response.Model;
}
}
public FileStreamDownloadModel GetAttachmentStream( long id )
{
// since we're returning the stream, we can't dispose of the response when done.
var response = GetAttachmentResponse<FileStreamDownloadModel>( id );
response.Model.Stream = response.Response.GetResponseStream();
return response.Model;
}
public interface IDownloadModel
{
string ContentType { get; }
string Name { get; set; }
}
Model classes
public class FileDownloadModel : IDownloadModel
{
public byte[] Content { get; set; }
public string Name { get; set; }
public string ContentType { get { return "application/octet-stream"; } }
}
public class FileStreamDownloadModel : IDownloadModel
{
public Stream Stream { get; set; }
public string Name { get; set; }
public string ContentType { get { return "application/octet-stream"; } }
}

I would suggest a variant on Option 1 [call it Option 1(a)].
Instead of generating a one-time token, "borrow" the MVC AntiForgeryToken classes, and have your parent application return a custom token and cookie to the child app for inclusion in the form returned to the user.
If the child application may have links for multiple documents on a single page, in the request for the token information, have the child app submit a unique identifier (identifying the page request from the user) as part of the request. You can then use this identifier in generating the tokens, and you can store the identifier as part of the verification process. This will give you a multi-use token, unique for each link on the page.
Slap an expiration time on the unique identifier, and you should be good to go.

Related

How Data is posted (POST) to Service in Servicestack , is it through URL?

I have complex requstDto which composed of other list of DTO's (Entity framework Entities) like
[Route("/demoservice/{Userdemo}/{EmployerDemoid}/{ReportDemo}/{DemoselectedDataList}/", "POST")]
public class ReportDemo : IReturn<String>
{
public List<selectedidList> selectedDataList{ get; set; }
}
where UserReport is follows
public class UserReport
{
public string UserName { get; set; }
public Datetime CreatedON{ get; set; }
}
when i try to post to request it gives me following error
A potentially dangerous Request.Path value was detected from the client (:)
i think it gives error due to : in CreatedON field ( for time part).
is the post values are also sent through URL to ServiceStack URL ? if yes
1) what if we have very large and complex requestDTO resulting into large number of characters (greater than allowed )in URL?
2) how to make above scenario work as ":" is reserved and cant be sent through URL?
3) How to see request URL Generated from client ?
My Client code in MVC.net is
var client = new JsonServiceClient(ConfigurationManager.AppSettings["applicationUrl"])
{
//for windows authentication
Credentials = CredentialCache.DefaultCredentials
};
var result = client.Post (new ReportDemo
{
UserName = model.UserName,
EmployerID = model.EmployerID,
Report = model.Report,
selectedDataList =userReportViewModel.selectedDataList
});
Thanks in advance,
Amol
Only the /path/info of the Url should be specified in the [Route]. Ideally routes should use a human-readable logically-structured Url that refers to a "Resource" (noun). See the SeviceStack REST Events Example for different examples.
Routes should also never include complex types and any variable that isn't on the [Route] is automatically sent in the HTTP Request Body for POST requests or the QueryString from GET Requests.
For a User Report like this I would choose a URL that identifies the report, if the report has a name like "Demo Report" I would use a path info like:
[Route("/reports/demo")]
public class ReportDemo : IReturn<String> { ... }
Otherwise if this is a Report for Users you may instead want to use something like:
[Route("/users/{UserName}/reports/demo")]
public class ReportDemo : IReturn<String> { ... }
You can check what url is used by using the Reverse Routing Extension methods, e.g:
var request = ReportDemo { UserName = "Foo", ... };
request.ToPostUrl().Print(); //= /users/Foo/reports/demo
Now you can send your Request with any property not in the Route getting POST'ed to the above url, e.g:
string result = client.Post (new ReportDemo {
UserName = userReportViewModel.UserName,
EmployerID = userReportViewModel.EmployerID,
Report = userReportViewModel.Report,
selectedDataList =userReportViewModel.selectedDataList
});
If your Report does return a string you can use IReturn<string> however if it returns a Response DTO you'll want to use that instead, e.g IReturn<ReportDemoResponse>.

Displaying the fullname (firstname, lastname) of the logged in user

I work on an ASP.NET MVC4 solution. When the user is logged in, I would like to display his fullname (not the username provided in the login form). His fullname (firstname + lastname actually stored in the user table in my database) should be displayed in the top right corner.
For better performance, I don't want to query the database each time a request is done.
How to proceed?
Keeping the user information (firstname, lastname, ...) in a cookie?
Keeping the user information is a session variable for all the lifecycle of the application?
Keeping the user information in a 'Profile' like explained here: How to assign Profile values? (*)
Something else?
(*) I think this solution a little complex for the use I have.
Thanks.
I would use a cookie. It doesn't hog up any memory on your machine like Session, and it doesn't hit the database like Profile would. Just remember to delete the cookie when the user signs off.
Note that the Profile would hit the database server each time you make a request. As far as I know, Profile data is not cached anywhere on the web server (unless you have a custom profile provider).
Another reason why I like cookie: if you ever want to store any additional user information for fast access, like UserPrimaryKey, or any special user preferences, you can just store them as JSON in the cookie. Here is an example:
Another note: the code below uses Newtonsoft.Json (the JsonConvert lines). It should come out of the box in an MVC4 project, but for an MVC3 project, you can just add it via nuget.
public class UserCacheModel
{
public string FullName { get; set; }
public string Preference1 { get; set; }
public int Preference2 { get; set; }
public bool PreferenceN { get; set; }
}
public static class UserCacheExtensions
{
private const string CookieName = "UserCache";
// put the info in a cookie
public static void UserCache(this HttpResponseBase response, UserCacheModel info)
{
// serialize model to json
var json = JsonConvert.SerializeObject(info);
// create a cookie
var cookie = new HttpCookie(CookieName, json)
{
// I **think** if you omit this property, it will tell the browser
// to delete the cookie when the user closes the browser window
Expires = DateTime.UtcNow.AddDays(60),
};
// write the cookie
response.SetCookie(cookie);
}
// get the info from cookie
public static UserCacheModel UserCache(this HttpRequestBase request)
{
// default user cache is empty
var json = "{}";
// try to get user cache json from cookie
var cookie = request.Cookies.Get(CookieName);
if (cookie != null)
json = cookie.Value ?? json;
// deserialize & return the user cache info from json
var userCache = JsonConvert.DeserializeObject<UserCacheModel>(json);
return userCache;
}
}
With this, you can read / write the cookie info from a controller like this:
// set the info
public ActionResult MyAction()
{
var fullName = MethodToGetFullName();
var userCache = new UserCache { FullName = fullName };
Response.UserCache(userCache);
return Redirect... // you must redirect to set the cookie
}
// get the info
public ActionResult MyOtherAction()
{
var userCache = Request.UserCache();
ViewBag.FullName = userCache.FullName;
return View();
}

How to use sessions in an ASP.NET MVC 4 application?

I am new to ASP.NET MVC. I have used PHP before and it was easy to create a session and select user records based on the current session variables.
I have looked everywhere on the Internet for a simple step-by-step tutorial that can show me how to create and use sessions in my C# ASP.NET MVC 4 application. I want to create a session with user variables that I can access from anywhere in my controllers and be able to use the variables in my LINQ queries.
Try
//adding data to session
//assuming the method below will return list of Products
var products=Db.GetProducts();
//Store the products to a session
Session["products"]=products;
//To get what you have stored to a session
var products=Session["products"] as List<Product>;
//to clear the session value
Session["products"]=null;
Due to the stateless nature of the web, sessions are also an extremely useful way of persisting objects across requests by serialising them and storing them in a session.
A perfect use case of this could be if you need to access regular information across your application, to save additional database calls on each request, this data can be stored in an object and unserialised on each request, like so:
Our reusable, serializable object:
[Serializable]
public class UserProfileSessionData
{
public int UserId { get; set; }
public string EmailAddress { get; set; }
public string FullName { get; set; }
}
Use case:
public class LoginController : Controller {
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
var profileData = new UserProfileSessionData {
UserId = model.UserId,
EmailAddress = model.EmailAddress,
FullName = model.FullName
}
this.Session["UserProfile"] = profileData;
}
}
public ActionResult LoggedInStatusMessage()
{
var profileData = this.Session["UserProfile"] as UserProfileSessionData;
/* From here you could output profileData.FullName to a view and
save yourself unnecessary database calls */
}
}
Once this object has been serialised, we can use it across all controllers without needing to create it or query the database for the data contained within it again.
Inject your session object using Dependency Injection
In a ideal world you would 'program to an interface, not implementation' and inject your serializable session object into your controller using your Inversion of Control container of choice, like so (this example uses StructureMap as it's the one I'm most familiar with).
public class WebsiteRegistry : Registry
{
public WebsiteRegistry()
{
this.For<IUserProfileSessionData>().HybridHttpOrThreadLocalScoped().Use(() => GetUserProfileFromSession());
}
public static IUserProfileSessionData GetUserProfileFromSession()
{
var session = HttpContext.Current.Session;
if (session["UserProfile"] != null)
{
return session["UserProfile"] as IUserProfileSessionData;
}
/* Create new empty session object */
session["UserProfile"] = new UserProfileSessionData();
return session["UserProfile"] as IUserProfileSessionData;
}
}
You would then register this in your Global.asax.cs file.
For those that aren't familiar with injecting session objects, you can find a more in-depth blog post about the subject here.
A word of warning:
It's worth noting that sessions should be kept to a minimum, large sessions can start to cause performance issues.
It's also recommended to not store any sensitive data in them (passwords, etc).
This is how session state works in ASP.NET and ASP.NET MVC:
ASP.NET Session State Overview
Basically, you do this to store a value in the Session object:
Session["FirstName"] = FirstNameTextBox.Text;
To retrieve the value:
var firstName = Session["FirstName"];
You can store any kind of data in a session using:
Session["VariableName"]=value;
This variable will last 20 mins or so.
U can store any value in session like
Session["FirstName"] = FirstNameTextBox.Text;
but i will suggest u to take as static field in model assign value to it and u can access that field value any where in application. U don't need session. session should be avoided.
public class Employee
{
public int UserId { get; set; }
public string EmailAddress { get; set; }
public static string FullName { get; set; }
}
on controller - Employee.FullName = "ABC";
Now u can access this full Name anywhere in application.

MVC3 – ViewModels and controller functionalty: suggested design patterns

I have built a simple MVC3-based ticket entry site for a less-than-usable call center application and am attempting to refactor my prototype to better adhere to design patterns partly to make it more maintainable going forward but mostly as a learning exercise.
The user-facing view is a form consisting of basic user information in addition to a few panels allowing selection of various resource types. Each resource type (hardware, software, etc) is displayed in the same way: using dual, filterable listboxes with add/remove buttons, an optional “justification” textarea that conditionally displays if a requested resource requires justification, and general comments.
I have built the following ViewModel for the individual panels:
public class RequestableList
{
// list of requestable items ids requiring justification
private List<string> _restrictedItems = new List<string>();
public List<string> RestrictedItems
{
get { return _restrictedItems; }
set { _restrictedItems = value; }
}
// the key-value pairs from which to populate available items list
private Dictionary<string, string> _availableItems = new Dictionary<string, string>();
public Dictionary<string, string> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
// item ids requested by user
private List<string> _requestedItems = new List<string>();
public List<string> RequestedItems
{
get { return _requestedItems; }
set { _requestedItems = value; }
}
}
The main ViewModel is then comprised of multiple RequestableLists as necessary:
public class SimpleRequestViewModel
{
public UserInfo userInfo { get; set; }
public RequestableList Software {get;set;}
public RequestableList Hardware {get;set;}
public RequestableList Access {get;set;}
public string SoftwareAdditionalInfo { get; set; }
public string HardwareAdditionalInfo { get; set; }
public string AccessFileMailShare { get; set; }
public string AccessAdditionalInfo { get; set; }
public string SoftwareJustification { get; set; }
public string HardwareJustification { get; set; }
public string AccessJustification { get; set; }
public string Comment { get; set; }
}
I have created a strongly typed view for SimpleRequestViewModel (and its variant) and a strongly typed EditorTemplate for RequestableList that wires up the dual listboxes, filtering, and jquery. All renders well and is working but the code currently smells.
When posting to the controller, if the model is valid I must translate it into a readable text description in order to create a new ticket in in the call center app. It doesn’t feel right to have the controller performing that translation into readable text but I run into hurdles when trying to design another class to translate the viewmodels.
Only the selected item values are posted so before translating the request into text I must first lookup the appropriate text for the provided values (they are required in description). The controller is currently the only object that has access to the call center data model for this lookup query.
There are 2 similar ViewModels containing varying combinations of RequestableLists so any translator must be able to translate the various combinations. One has only Hardware and Software, another may have Hardware Software, and a few more RequestableLists.
I considered overriding ToString() directly in the ViewModel but didn’t like that business logic (conditional rendering) there, and again, once posted, the ViewModel doesn’t contain the text for the selected items in the listbox so it would need access to the data model.
The translation of posted values to text as it is currently handled in the controller smells as it’s handled in a switch statement. The controller takes each posted RequestableList and populates the original “Available” fields before it builds the new ticket description.
switch (requestCategory)
{
case RequestableCategory.Software:
itemList = sde.GetSoftware();
break;
case RequestableCategory.Hardware:
itemList = sde.GetHardware();
break;
case RequestableCategory.Access:
itemList = sde.GetAccess();
break;
case RequestableCategory.Telecom:
itemList = sde.GetTelecom();
break;
default:
throw new ArgumentException();
}
So, my question(s):
What patterns are techniques would you recommend for performing the posted viewmodel to ticket description translation?
How do you typically handle the “only posts value” issue with select boxes when you need the text as well as the value?
Is there a better way for me to be approaching this problem?
Again, I am hoping this is a learning experience for me and am more than willing to provide additional information or description if needed.
A few suggestions:
Abstract the logic that does the call center submission into its own class. Provide (from the controller) whatever dependencies it needs to access the call center DB. Have different methods to handle the various types of view models using overloading. Presumably the descriptions come from the DB so you can extract the description from the DB based on the value in this class. This class could also take responsibility for building your view models for the display actions as well. Note that with this pattern the class can interact with the DB directly, through a repository, or even via web services/an API.
Use a repository pattern that implements some caching if performance is an issue in looking up the description from the DB the second time. I suspect it won't be unless your call center is very large, but that would be the place to optimize the query logic. The repository can be the thing that the controller passes to the submission class.
If you don't need to access the DB directly in the controller, consider passing the broker class as a dependency directly.
It might look like:
private ICallCenterBroker CallCenterBroker { get; set; }
public RequestController( ICallCenterBroker broker )
{
this.CallCenterBroker = broker;
// if not using DI, instantiate a new one
// this.CallCenterBroker = broker ?? new CallCenterBroker( new CallCenterRepository() );
}
[HttpGet]
public ActionResult CreateSimple()
{
var model = this.CallCenterBroker.CreateSimpleModel( this.User.Identity.Name );
return View( model );
}
[HttpPost]
public ActionResult CreateSimple( SimpleRequestViewModel request )
{
if (Model.IsValid)
{
var ticket = this.CallCenterBroker.CreateTicket( request );
// do something with ticket, perhaps create a different model for display?
this.CallCenterBroker.SubmitTicket( ticket );
return RedirectToAction( "index" ); // list all requests?
}
return View();
}

How do I specify multiple keys in DataServiceKey attribute for WCF Data Service data context object

I am using Reflection provider for my WCF Data Service and my Data Context object has two key members, say EmpId and DeptId.
If I specify [DataServiceKey("EmpId", "DeptId")], the service doesn't work. When I try to access the collection with the URL http://localhost:55389/DataService.svc/EmployeeData, I get the following error:
The XML page cannot be displayed
Cannot view XML input using XSL style
sheet. Please correct the error and
then click the Refresh button, or try
again later. The following tags were
not closed: feed. Error processing
resource
'http://localhost:55389/DataService.svc/EmployeeData'.
With single member in the DataServiceKey, it works fine. I tried with Custom Data Provider and I could achieve this functionality. But if I can do it with the Reflection provider, that would be great.
I don't think the problem is the multiple keys. To confirm please use for example Fiddler or something similar to grab the whole response from the server and share the error in it (as I'm sure there will be one in there).
Guessing from the description I think the problem is that one of your key property values is null. That is not supported and would cause so called in-stream error which would leave the response XML incomplete (which seems to be your case).
OData can handle multiple keys but all keys must have a valid value. Review this for OData's rule. If you want to retrieve an entry with EmpId=1 and DeptId=someString, you should reconstruct your URI into something like:
http://localhost:55389/DataService.svc/EmployeeData(EmpId=1,DeptId='someString')
Be careful in OData queries because they are case sensitive.
That is weird, I just tried this:
public class Context
{
public IQueryable<Person> People {
get {
return (new List<Person> {
new Person { EmpId = 1, DeptId = 2, Name = "Dude" }
}).AsQueryable();
}
}
}
[DataServiceKey("EmpId", "DeptId")]
public class Person
{
public int EmpId { get; set; }
public int DeptId { get; set; }
public string Name { get; set; }
}
public class WcfDataService1 : DataService<Context>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion =
DataServiceProtocolVersion.V2;
}
}
And it works just fine, do you notice any major differences?
-Alex

Resources